mirror of
https://github.com/Dadido3/noita-mapcap.git
synced 2024-11-22 21:17: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.
|
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
|
imageMutex *sync.RWMutex //
|
||||||
|
imageUsedFlag bool // Flag signalling, that the image was used recently
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *imageTile) GetImage() (*image.RGBA, error) {
|
func (it *imageTile) GetImage() (*image.RGBA, error) {
|
||||||
it.imageMutex.Lock()
|
it.imageMutex.RLock()
|
||||||
defer it.imageMutex.Unlock() // TODO: Use RWMutex
|
|
||||||
|
it.imageUsedFlag = true // Race condition may happen on this flag, but doesn't matter here.
|
||||||
|
|
||||||
// Check if the image is already loaded
|
// 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 {
|
if img, ok := it.image.(*image.RGBA); ok {
|
||||||
return img, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
@ -58,7 +71,10 @@ func (it *imageTile) GetImage() (*image.RGBA, error) {
|
|||||||
|
|
||||||
// Free the image after some time
|
// Free the image after some time
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(5 * time.Second)
|
for it.imageUsedFlag {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
it.imageUsedFlag = false
|
||||||
|
}
|
||||||
|
|
||||||
it.imageMutex.Lock()
|
it.imageMutex.Lock()
|
||||||
defer it.imageMutex.Unlock()
|
defer it.imageMutex.Unlock()
|
||||||
@ -69,15 +85,15 @@ func (it *imageTile) GetImage() (*image.RGBA, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (it *imageTile) OffsetBounds() image.Rectangle {
|
func (it *imageTile) OffsetBounds() image.Rectangle {
|
||||||
it.imageMutex.Lock()
|
it.imageMutex.RLock()
|
||||||
defer it.imageMutex.Unlock() // TODO: Use RWMutex
|
defer it.imageMutex.RUnlock()
|
||||||
|
|
||||||
return it.image.Bounds().Add(it.offset)
|
return it.image.Bounds().Add(it.offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *imageTile) Bounds() image.Rectangle {
|
func (it *imageTile) Bounds() image.Rectangle {
|
||||||
it.imageMutex.Lock()
|
it.imageMutex.RLock()
|
||||||
defer it.imageMutex.Unlock() // TODO: Use RWMutex
|
defer it.imageMutex.RUnlock()
|
||||||
|
|
||||||
return it.image.Bounds()
|
return it.image.Bounds()
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ 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{},
|
imageMutex: &sync.RWMutex{},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,12 +206,21 @@ func (tp tilePairs) AlignTiles(tiles []*imageTile) error {
|
|||||||
|
|
||||||
func (tp tilePairs) Stitch(tiles []imageTile, destImage *image.RGBA) error {
|
func (tp tilePairs) Stitch(tiles []imageTile, destImage *image.RGBA) error {
|
||||||
intersectTiles := []*imageTile{}
|
intersectTiles := []*imageTile{}
|
||||||
|
images := []*image.RGBA{}
|
||||||
|
|
||||||
// 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.OffsetBounds().Overlaps(destImage.Bounds()) {
|
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))
|
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.
|
// 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawMedianBlended(tiles []*imageTile, destImage *image.RGBA) error {
|
func drawMedianBlended(images []*image.RGBA, destImage *image.RGBA) {
|
||||||
bounds := destImage.Bounds()
|
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 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++ {
|
||||||
rList, gList, bList := []int16{}, []int16{}, []int16{}
|
rList, gList, bList := rListEmpty, gListEmpty, bListEmpty
|
||||||
point := image.Point{ix, iy}
|
point := image.Point{ix, iy}
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
// Iterate through all tiles, and create a list of colors.
|
// Iterate through all images and create a list of colors.
|
||||||
for _, tile := range tiles {
|
for _, img := range images {
|
||||||
tilePoint := point.Sub(tile.offset)
|
if point.In(img.Bounds()) {
|
||||||
imageRGBA, err := tile.GetImage() // TODO: Optimize, as it's slow to get tiles and images every pixel
|
col := img.RGBAAt(point.X, point.Y)
|
||||||
if err != nil {
|
rList, gList, bList = append(rList, int(col.R)), append(gList, int(col.G)), append(bList, int(col.B))
|
||||||
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))
|
|
||||||
found = true
|
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 {
|
if !found {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort rList.
|
// Sort colors.
|
||||||
sort.Slice(rList, func(i, j int) bool {
|
sort.Ints(rList)
|
||||||
return rList[i] < rList[j]
|
sort.Ints(gList)
|
||||||
})
|
sort.Ints(bList)
|
||||||
|
|
||||||
// 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]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Take the middle element of each color.
|
// Take the middle element of each color.
|
||||||
var r, g, b uint8
|
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})
|
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))
|
log.Printf("Got %v tiles", len(tiles))
|
||||||
|
|
||||||
/*f, err := os.Create("cpu.prof")
|
/*profFile, err := os.Create("cpu.prof")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("could not create CPU profile: %v", err)
|
log.Panicf("could not create CPU profile: %v", err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer profFile.Close()
|
||||||
if err := pprof.StartCPUProfile(f); err != nil {
|
if err := pprof.StartCPUProfile(profFile); err != nil {
|
||||||
log.Panicf("could not start CPU profile: %v", err)
|
log.Panicf("could not start CPU profile: %v", err)
|
||||||
}
|
}
|
||||||
defer pprof.StopCPUProfile()*/
|
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())
|
log.Printf("Creating output image with a size of %v", outputRect.Size())
|
||||||
outputImage := image.NewRGBA(outputRect)
|
outputImage := image.NewRGBA(outputRect)
|
||||||
|
|
||||||
log.Printf("Stitching %v tiles into an image at %v", len(tiles), outputImage.Bounds())
|
log.Printf("Stitching %v tiles into an image at %v", len(tiles), outputImage.Bounds())
|
||||||
tp := make(tilePairs)
|
tp := make(tilePairs)
|
||||||
if err := tp.StitchGrid(tiles, outputImage, 256); err != nil {
|
if err := tp.StitchGrid(tiles, outputImage, 1024); err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user