mirror of
https://github.com/Dadido3/noita-mapcap.git
synced 2024-12-26 06:27:33 +00:00
Reduce stitching time
About three times as fast
This commit is contained in:
parent
b11ba98db7
commit
b53ec0d60b
@ -19,15 +19,28 @@ type imageTile struct {
|
||||
|
||||
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.
|
||||
imageMutex *sync.Mutex
|
||||
image image.Image // Either a rectangle or an RGBA image. The bounds of this image are determined by the filename.
|
||||
imageMutex *sync.RWMutex //
|
||||
imageUsedFlag bool // Flag signalling, that the image was used recently
|
||||
}
|
||||
|
||||
func (it *imageTile) GetImage() (*image.RGBA, error) {
|
||||
it.imageMutex.Lock()
|
||||
defer it.imageMutex.Unlock() // TODO: Use RWMutex
|
||||
it.imageMutex.RLock()
|
||||
|
||||
it.imageUsedFlag = true // Race condition may happen on this flag, but doesn't matter here.
|
||||
|
||||
// Check if the image is already loaded
|
||||
if img, ok := it.image.(*image.RGBA); ok {
|
||||
it.imageMutex.RUnlock()
|
||||
return img, nil
|
||||
}
|
||||
|
||||
it.imageMutex.RUnlock()
|
||||
// It's possible that the image got changed in between here
|
||||
it.imageMutex.Lock()
|
||||
defer it.imageMutex.Unlock()
|
||||
|
||||
// Check again if the image is already loaded
|
||||
if img, ok := it.image.(*image.RGBA); ok {
|
||||
return img, nil
|
||||
}
|
||||
@ -58,7 +71,10 @@ func (it *imageTile) GetImage() (*image.RGBA, error) {
|
||||
|
||||
// Free the image after some time
|
||||
go func() {
|
||||
time.Sleep(5 * time.Second)
|
||||
for it.imageUsedFlag {
|
||||
time.Sleep(5 * time.Second)
|
||||
it.imageUsedFlag = false
|
||||
}
|
||||
|
||||
it.imageMutex.Lock()
|
||||
defer it.imageMutex.Unlock()
|
||||
@ -69,15 +85,15 @@ func (it *imageTile) GetImage() (*image.RGBA, error) {
|
||||
}
|
||||
|
||||
func (it *imageTile) OffsetBounds() image.Rectangle {
|
||||
it.imageMutex.Lock()
|
||||
defer it.imageMutex.Unlock() // TODO: Use RWMutex
|
||||
it.imageMutex.RLock()
|
||||
defer it.imageMutex.RUnlock()
|
||||
|
||||
return it.image.Bounds().Add(it.offset)
|
||||
}
|
||||
|
||||
func (it *imageTile) Bounds() image.Rectangle {
|
||||
it.imageMutex.Lock()
|
||||
defer it.imageMutex.Unlock() // TODO: Use RWMutex
|
||||
it.imageMutex.RLock()
|
||||
defer it.imageMutex.RUnlock()
|
||||
|
||||
return it.image.Bounds()
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ func loadImages(path string) ([]imageTile, error) {
|
||||
imageTiles = append(imageTiles, imageTile{
|
||||
fileName: file,
|
||||
image: image.Rect(x, y, x+width, y+height),
|
||||
imageMutex: &sync.Mutex{},
|
||||
imageMutex: &sync.RWMutex{},
|
||||
})
|
||||
}
|
||||
|
||||
@ -206,12 +206,21 @@ func (tp tilePairs) AlignTiles(tiles []*imageTile) error {
|
||||
|
||||
func (tp tilePairs) Stitch(tiles []imageTile, destImage *image.RGBA) error {
|
||||
intersectTiles := []*imageTile{}
|
||||
images := []*image.RGBA{}
|
||||
|
||||
// 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.
|
||||
for i, tile := range tiles {
|
||||
if tile.OffsetBounds().Overlaps(destImage.Bounds()) {
|
||||
intersectTiles = append(intersectTiles, &tiles[i])
|
||||
tilePtr := &tiles[i]
|
||||
intersectTiles = append(intersectTiles, tilePtr)
|
||||
img, err := tilePtr.GetImage()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Couldn't get image: %w", err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,7 +242,9 @@ func (tp tilePairs) Stitch(tiles []imageTile, destImage *image.RGBA) error {
|
||||
drawLabel(destImage, intersectTile.image.Bounds().Min.X, intersectTile.image.Bounds().Min.Y, fmt.Sprintf("%v", intersectTile.fileName))
|
||||
}*/
|
||||
|
||||
return drawMedianBlended(intersectTiles, destImage)
|
||||
drawMedianBlended(images, destImage)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StitchGrid calls stitch, but divides the workload into a grid of chunks.
|
||||
@ -275,48 +286,36 @@ func (tp tilePairs) StitchGrid(tiles []imageTile, destImage *image.RGBA, gridSiz
|
||||
return
|
||||
}
|
||||
|
||||
func drawMedianBlended(tiles []*imageTile, destImage *image.RGBA) error {
|
||||
func drawMedianBlended(images []*image.RGBA, destImage *image.RGBA) {
|
||||
bounds := destImage.Bounds()
|
||||
|
||||
// Create arrays to be reused every pixel
|
||||
rListEmpty, gListEmpty, bListEmpty := make([]int, 0, len(images)), make([]int, 0, len(images)), make([]int, 0, len(images))
|
||||
|
||||
for iy := bounds.Min.Y; iy < bounds.Max.Y; iy++ {
|
||||
for ix := bounds.Min.X; ix < bounds.Max.X; ix++ {
|
||||
rList, gList, bList := []int16{}, []int16{}, []int16{}
|
||||
rList, gList, bList := rListEmpty, gListEmpty, bListEmpty
|
||||
point := image.Point{ix, iy}
|
||||
found := false
|
||||
|
||||
// Iterate through all tiles, and create a list of colors.
|
||||
for _, tile := range tiles {
|
||||
tilePoint := point.Sub(tile.offset)
|
||||
imageRGBA, err := tile.GetImage() // TODO: Optimize, as it's slow to get tiles and images every pixel
|
||||
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)
|
||||
rList, gList, bList = append(rList, int16(col.R)), append(gList, int16(col.G)), append(bList, int16(col.B))
|
||||
// Iterate through all images and create a list of colors.
|
||||
for _, img := range images {
|
||||
if point.In(img.Bounds()) {
|
||||
col := img.RGBAAt(point.X, point.Y)
|
||||
rList, gList, bList = append(rList, int(col.R)), append(gList, int(col.G)), append(bList, int(col.B))
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
// If there were no tiles to get data from, ignore the pixel.
|
||||
// If there were no images to get data from, ignore the pixel.
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
// Sort rList.
|
||||
sort.Slice(rList, func(i, j int) bool {
|
||||
return rList[i] < rList[j]
|
||||
})
|
||||
|
||||
// Sort gList.
|
||||
sort.Slice(gList, func(i, j int) bool {
|
||||
return gList[i] < gList[j]
|
||||
})
|
||||
|
||||
// Sort bList.
|
||||
sort.Slice(bList, func(i, j int) bool {
|
||||
return bList[i] < bList[j]
|
||||
})
|
||||
// Sort colors.
|
||||
sort.Ints(rList)
|
||||
sort.Ints(gList)
|
||||
sort.Ints(bList)
|
||||
|
||||
// Take the middle element of each color.
|
||||
var r, g, b uint8
|
||||
@ -345,6 +344,4 @@ func drawMedianBlended(tiles []*imageTile, destImage *image.RGBA) error {
|
||||
destImage.SetRGBA(ix, iy, color.RGBA{r, g, b, 255})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -23,24 +23,26 @@ func main() {
|
||||
}
|
||||
log.Printf("Got %v tiles", len(tiles))
|
||||
|
||||
/*f, err := os.Create("cpu.prof")
|
||||
/*profFile, err := os.Create("cpu.prof")
|
||||
if err != nil {
|
||||
log.Panicf("could not create CPU profile: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
if err := pprof.StartCPUProfile(f); err != nil {
|
||||
defer profFile.Close()
|
||||
if err := pprof.StartCPUProfile(profFile); err != nil {
|
||||
log.Panicf("could not start CPU profile: %v", err)
|
||||
}
|
||||
defer pprof.StopCPUProfile()*/
|
||||
|
||||
outputRect := image.Rect(-10000, -10000, 10000, 10000)
|
||||
// TODO: Flags / Program arguments
|
||||
|
||||
outputRect := image.Rect(-35000, -35000, 35000, 35000)
|
||||
|
||||
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, 256); err != nil {
|
||||
if err := tp.StitchGrid(tiles, outputImage, 1024); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user