mirror of
				https://github.com/Dadido3/noita-mapcap.git
				synced 2025-11-04 07:19:34 +00:00 
			
		
		
		
	Several changes
- Add compatibility for newest Noita beta - Modify STREAMING_CHUNK_TARGET, GRID_MAX_UPDATES_PER_FRAME and GRID_MIN_UPDATES_PER_FRAME magic numbers for a more robust capturing process - Add LimitGroup to util.go - Add webp-level command line flag to define the webp compression level - Rework progress bar to make it work in DZI export mode - Refactor image exporter functions - Use LimitGroup to make DZI export multithreaded - Add BlendMethodFast which doesn't mix tile pixels - Up Go version to 1.22 - Use Dadido3/go-libwebp for WebP encoding
This commit is contained in:
		
							parent
							
								
									47d570014d
								
							
						
					
					
						commit
						b1a10870c1
					
				
							
								
								
									
										2
									
								
								.github/workflows/build-release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/build-release.yml
									
									
									
									
										vendored
									
									
								
							@ -18,7 +18,7 @@ jobs:
 | 
				
			|||||||
      - name: Set up Go
 | 
					      - name: Set up Go
 | 
				
			||||||
        uses: actions/setup-go@v2
 | 
					        uses: actions/setup-go@v2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          go-version: ^1.21
 | 
					          go-version: ^1.22
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Check out code into the Go module directory
 | 
					      - name: Check out code into the Go module directory
 | 
				
			||||||
        uses: actions/checkout@v2
 | 
					        uses: actions/checkout@v2
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/build-test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/build-test.yml
									
									
									
									
										vendored
									
									
								
							@ -12,7 +12,7 @@ jobs:
 | 
				
			|||||||
    - name: Set up Go
 | 
					    - name: Set up Go
 | 
				
			||||||
      uses: actions/setup-go@v2
 | 
					      uses: actions/setup-go@v2
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        go-version: ^1.21
 | 
					        go-version: ^1.22
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Check out code into the Go module directory
 | 
					    - name: Check out code into the Go module directory
 | 
				
			||||||
      uses: actions/checkout@v2
 | 
					      uses: actions/checkout@v2
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@ -7,6 +7,7 @@
 | 
				
			|||||||
        "basicfont",
 | 
					        "basicfont",
 | 
				
			||||||
        "bytecode",
 | 
					        "bytecode",
 | 
				
			||||||
        "cheggaaa",
 | 
					        "cheggaaa",
 | 
				
			||||||
 | 
					        "Dadido",
 | 
				
			||||||
        "dofile",
 | 
					        "dofile",
 | 
				
			||||||
        "dont",
 | 
					        "dont",
 | 
				
			||||||
        "Downscales",
 | 
					        "Downscales",
 | 
				
			||||||
@ -26,6 +27,7 @@
 | 
				
			|||||||
        "Lanczos",
 | 
					        "Lanczos",
 | 
				
			||||||
        "lann",
 | 
					        "lann",
 | 
				
			||||||
        "ldflags",
 | 
					        "ldflags",
 | 
				
			||||||
 | 
					        "libwebp",
 | 
				
			||||||
        "linearize",
 | 
					        "linearize",
 | 
				
			||||||
        "longleg",
 | 
					        "longleg",
 | 
				
			||||||
        "lowram",
 | 
					        "lowram",
 | 
				
			||||||
@ -57,6 +59,7 @@
 | 
				
			|||||||
        "Vogel",
 | 
					        "Vogel",
 | 
				
			||||||
        "Voronoi",
 | 
					        "Voronoi",
 | 
				
			||||||
        "webp",
 | 
					        "webp",
 | 
				
			||||||
 | 
					        "wepb",
 | 
				
			||||||
        "xmax",
 | 
					        "xmax",
 | 
				
			||||||
        "xmin",
 | 
					        "xmin",
 | 
				
			||||||
        "ymax",
 | 
					        "ymax",
 | 
				
			||||||
 | 
				
			|||||||
@ -36,7 +36,7 @@ example list of files:
 | 
				
			|||||||
    If set to 1, only the newest tile will be used for any resulting pixel.
 | 
					    If set to 1, only the newest tile will be used for any resulting pixel.
 | 
				
			||||||
    Use 1 to prevent ghosting and blurry objects.
 | 
					    Use 1 to prevent ghosting and blurry objects.
 | 
				
			||||||
  - `input string`
 | 
					  - `input string`
 | 
				
			||||||
    The source path of the image tiles to be stitched. Defaults to "./..//..//output")
 | 
					    The source path of the image tiles to be stitched. Defaults to "./..//..//output"
 | 
				
			||||||
  - `entities string`
 | 
					  - `entities string`
 | 
				
			||||||
    The path to the `entities.json` file. This contains Noita specific entity data. Defaults to "./../../output/entities.json".
 | 
					    The path to the `entities.json` file. This contains Noita specific entity data. Defaults to "./../../output/entities.json".
 | 
				
			||||||
  - `player-path string`
 | 
					  - `player-path string`
 | 
				
			||||||
@ -48,6 +48,8 @@ example list of files:
 | 
				
			|||||||
    The size of the resulting deep zoom image (DZI) tiles in pixels. Defaults to 512.
 | 
					    The size of the resulting deep zoom image (DZI) tiles in pixels. Defaults to 512.
 | 
				
			||||||
  - `dzi-tile-overlap`
 | 
					  - `dzi-tile-overlap`
 | 
				
			||||||
    The number of additional pixels around every deep zoom image (DZI) tile. Defaults to 2.
 | 
					    The number of additional pixels around every deep zoom image (DZI) tile. Defaults to 2.
 | 
				
			||||||
 | 
					  - `wepb-level`
 | 
				
			||||||
 | 
					    Compression level of WebP files, from 0 (fast) to 9 (slow, best compression). Defaults to 8.
 | 
				
			||||||
  - `xmax int`
 | 
					  - `xmax int`
 | 
				
			||||||
    Right bound of the output rectangle. This coordinate is not included in the output.
 | 
					    Right bound of the output rectangle. This coordinate is not included in the output.
 | 
				
			||||||
  - `xmin int`
 | 
					  - `xmin int`
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
// Copyright (c) 2022 David Vogel
 | 
					// Copyright (c) 2022-2024 David Vogel
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// This software is released under the MIT License.
 | 
					// This software is released under the MIT License.
 | 
				
			||||||
// https://opensource.org/licenses/MIT
 | 
					// https://opensource.org/licenses/MIT
 | 
				
			||||||
@ -8,6 +8,7 @@ package main
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"image"
 | 
						"image"
 | 
				
			||||||
	"image/color"
 | 
						"image/color"
 | 
				
			||||||
 | 
						"image/draw"
 | 
				
			||||||
	"math"
 | 
						"math"
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -106,7 +107,7 @@ func (b BlendMethodVoronoi) Draw(tiles []*ImageTile, destImage *image.RGBA) {
 | 
				
			|||||||
		images = append(images, tile.GetImage())
 | 
							images = append(images, tile.GetImage())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Create arrays to be reused every pixel.
 | 
						// Create color variables reused every pixel.
 | 
				
			||||||
	var col color.RGBA
 | 
						var col color.RGBA
 | 
				
			||||||
	var centerDistSqrMin int
 | 
						var centerDistSqrMin int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -147,3 +148,17 @@ func (b BlendMethodVoronoi) Draw(tiles []*ImageTile, destImage *image.RGBA) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BlendMethodFast just draws all tiles into the destination image.
 | 
				
			||||||
 | 
					// No mixing is done, and this is very fast when there is no or minimal tile overlap.
 | 
				
			||||||
 | 
					type BlendMethodFast struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Draw implements the StitchedImageBlendMethod interface.
 | 
				
			||||||
 | 
					func (b BlendMethodFast) Draw(tiles []*ImageTile, destImage *image.RGBA) {
 | 
				
			||||||
 | 
						for _, tile := range tiles {
 | 
				
			||||||
 | 
							if image := tile.GetImage(); image != nil {
 | 
				
			||||||
 | 
								bounds := image.Bounds()
 | 
				
			||||||
 | 
								draw.Draw(destImage, bounds, image, bounds.Min, draw.Src)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -12,9 +12,13 @@ import (
 | 
				
			|||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"runtime"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
 | 
						"sync/atomic"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/cheggaaa/pb/v3"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type DZI struct {
 | 
					type DZI struct {
 | 
				
			||||||
@ -99,9 +103,49 @@ func (d DZI) ExportDZIDescriptor(outputPath string) error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ExportDZITiles exports the single image tiles for every zoom level.
 | 
					// ExportDZITiles exports the single image tiles for every zoom level.
 | 
				
			||||||
func (d DZI) ExportDZITiles(outputDir string) error {
 | 
					func (d DZI) ExportDZITiles(outputDir string, bar *pb.ProgressBar, webPLevel int) error {
 | 
				
			||||||
	log.Printf("Creating DZI tiles in %q.", outputDir)
 | 
						log.Printf("Creating DZI tiles in %q.", outputDir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const scaleDivider = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var exportedTiles atomic.Int64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If there is a progress bar, start a goroutine that regularly updates it.
 | 
				
			||||||
 | 
						// We will base that on the number of exported tiles.
 | 
				
			||||||
 | 
						if bar != nil {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Count final number of tiles.
 | 
				
			||||||
 | 
							bounds := d.stitchedImage.bounds
 | 
				
			||||||
 | 
							var finalTiles int64
 | 
				
			||||||
 | 
							for zoomLevel := d.maxZoomLevel; zoomLevel >= 0; zoomLevel-- {
 | 
				
			||||||
 | 
								for iY := 0; iY <= (bounds.Dy()-1)/d.tileSize; iY++ {
 | 
				
			||||||
 | 
									for iX := 0; iX <= (bounds.Dx()-1)/d.tileSize; iX++ {
 | 
				
			||||||
 | 
										finalTiles++
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								bounds = image.Rect(DivideFloor(bounds.Min.X, scaleDivider), DivideFloor(bounds.Min.Y, scaleDivider), DivideCeil(bounds.Max.X, scaleDivider), DivideCeil(bounds.Max.Y, scaleDivider))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							bar.SetRefreshRate(250 * time.Millisecond).SetTotal(finalTiles).Start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							done := make(chan struct{})
 | 
				
			||||||
 | 
							defer func() {
 | 
				
			||||||
 | 
								done <- struct{}{}
 | 
				
			||||||
 | 
								bar.SetCurrent(bar.Total()).Finish()
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							go func() {
 | 
				
			||||||
 | 
								ticker := time.NewTicker(250 * time.Millisecond)
 | 
				
			||||||
 | 
								for {
 | 
				
			||||||
 | 
									select {
 | 
				
			||||||
 | 
									case <-done:
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									case <-ticker.C:
 | 
				
			||||||
 | 
										bar.SetCurrent(exportedTiles.Load())
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Start with the highest zoom level (Where every world pixel is exactly mapped into one image pixel).
 | 
						// Start with the highest zoom level (Where every world pixel is exactly mapped into one image pixel).
 | 
				
			||||||
	// Generate all tiles for this level, and then stitch another image (scaled down by a factor of 2) based on the previously generated tiles.
 | 
						// Generate all tiles for this level, and then stitch another image (scaled down by a factor of 2) based on the previously generated tiles.
 | 
				
			||||||
	// Repeat this process until we have generated level 0.
 | 
						// Repeat this process until we have generated level 0.
 | 
				
			||||||
@ -120,6 +164,7 @@ func (d DZI) ExportDZITiles(outputDir string) error {
 | 
				
			|||||||
		imageTiles := ImageTiles{}
 | 
							imageTiles := ImageTiles{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Export tiles.
 | 
							// Export tiles.
 | 
				
			||||||
 | 
							lg := NewLimitGroup(runtime.NumCPU())
 | 
				
			||||||
		for iY := 0; iY <= (stitchedImage.bounds.Dy()-1)/d.tileSize; iY++ {
 | 
							for iY := 0; iY <= (stitchedImage.bounds.Dy()-1)/d.tileSize; iY++ {
 | 
				
			||||||
			for iX := 0; iX <= (stitchedImage.bounds.Dx()-1)/d.tileSize; iX++ {
 | 
								for iX := 0; iX <= (stitchedImage.bounds.Dx()-1)/d.tileSize; iX++ {
 | 
				
			||||||
				rect := image.Rect(iX*d.tileSize, iY*d.tileSize, iX*d.tileSize+d.tileSize, iY*d.tileSize+d.tileSize)
 | 
									rect := image.Rect(iX*d.tileSize, iY*d.tileSize, iX*d.tileSize+d.tileSize, iY*d.tileSize+d.tileSize)
 | 
				
			||||||
@ -127,11 +172,16 @@ func (d DZI) ExportDZITiles(outputDir string) error {
 | 
				
			|||||||
				rect = rect.Inset(-d.overlap)
 | 
									rect = rect.Inset(-d.overlap)
 | 
				
			||||||
				img := stitchedImage.SubStitchedImage(rect)
 | 
									img := stitchedImage.SubStitchedImage(rect)
 | 
				
			||||||
				filePath := filepath.Join(levelBasePath, fmt.Sprintf("%d_%d%s", iX, iY, d.fileExtension))
 | 
									filePath := filepath.Join(levelBasePath, fmt.Sprintf("%d_%d%s", iX, iY, d.fileExtension))
 | 
				
			||||||
				if err := exportWebPSilent(img, filePath); err != nil {
 | 
					 | 
				
			||||||
					return fmt.Errorf("failed to export WebP: %w", err)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
				scaleDivider := 2
 | 
									lg.Add(1)
 | 
				
			||||||
 | 
									go func() {
 | 
				
			||||||
 | 
										defer lg.Done()
 | 
				
			||||||
 | 
										if err := exportWebP(img, filePath, webPLevel); err != nil {
 | 
				
			||||||
 | 
											log.Printf("Failed to export WebP: %v", err)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										exportedTiles.Add(1)
 | 
				
			||||||
 | 
									}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				imageTiles = append(imageTiles, ImageTile{
 | 
									imageTiles = append(imageTiles, ImageTile{
 | 
				
			||||||
					fileName:         filePath,
 | 
										fileName:         filePath,
 | 
				
			||||||
					modTime:          time.Now(),
 | 
										modTime:          time.Now(),
 | 
				
			||||||
@ -143,11 +193,12 @@ func (d DZI) ExportDZITiles(outputDir string) error {
 | 
				
			|||||||
				})
 | 
									})
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							lg.Wait()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Create new stitched image from the previously exported tiles.
 | 
							// Create new stitched image from the previously exported tiles.
 | 
				
			||||||
		// The tiles are already created in a way, that they are scaled down by a factor of 2.
 | 
							// The tiles are already created in a way, that they are scaled down by a factor of 2.
 | 
				
			||||||
		var err error
 | 
							var err error
 | 
				
			||||||
		stitchedImage, err = NewStitchedImage(imageTiles, imageTiles.Bounds(), BlendMethodMedian{BlendTileLimit: 0}, 128, nil)
 | 
							stitchedImage, err = NewStitchedImage(imageTiles, imageTiles.Bounds(), BlendMethodFast{}, 128, nil)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return fmt.Errorf("failed to run NewStitchedImage(): %w", err)
 | 
								return fmt.Errorf("failed to run NewStitchedImage(): %w", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
// Copyright (c) 2023 David Vogel
 | 
					// Copyright (c) 2023-2024 David Vogel
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// This software is released under the MIT License.
 | 
					// This software is released under the MIT License.
 | 
				
			||||||
// https://opensource.org/licenses/MIT
 | 
					// https://opensource.org/licenses/MIT
 | 
				
			||||||
@ -10,9 +10,11 @@ import (
 | 
				
			|||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/cheggaaa/pb/v3"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func exportDZI(stitchedImage *StitchedImage, outputPath string, dziTileSize, dziOverlap int) error {
 | 
					func exportDZIStitchedImage(stitchedImage *StitchedImage, outputPath string, bar *pb.ProgressBar, dziTileSize, dziOverlap int, webPLevel int) error {
 | 
				
			||||||
	descriptorPath := outputPath
 | 
						descriptorPath := outputPath
 | 
				
			||||||
	extension := filepath.Ext(outputPath)
 | 
						extension := filepath.Ext(outputPath)
 | 
				
			||||||
	outputTilesPath := strings.TrimSuffix(outputPath, extension) + "_files"
 | 
						outputTilesPath := strings.TrimSuffix(outputPath, extension) + "_files"
 | 
				
			||||||
@ -30,7 +32,7 @@ func exportDZI(stitchedImage *StitchedImage, outputPath string, dziTileSize, dzi
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Export DZI tiles.
 | 
						// Export DZI tiles.
 | 
				
			||||||
	if err := dzi.ExportDZITiles(outputTilesPath); err != nil {
 | 
						if err := dzi.ExportDZITiles(outputTilesPath, bar, webPLevel); err != nil {
 | 
				
			||||||
		return fmt.Errorf("failed to export DZI tiles: %w", err)
 | 
							return fmt.Errorf("failed to export DZI tiles: %w", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
// Copyright (c) 2023 David Vogel
 | 
					// Copyright (c) 2023-2024 David Vogel
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// This software is released under the MIT License.
 | 
					// This software is released under the MIT License.
 | 
				
			||||||
// https://opensource.org/licenses/MIT
 | 
					// https://opensource.org/licenses/MIT
 | 
				
			||||||
@ -11,15 +11,44 @@ import (
 | 
				
			|||||||
	"image/jpeg"
 | 
						"image/jpeg"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/cheggaaa/pb/v3"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func exportJPEG(stitchedImage image.Image, outputPath string) error {
 | 
					func exportJPEGStitchedImage(stitchedImage *StitchedImage, outputPath string, bar *pb.ProgressBar) error {
 | 
				
			||||||
	log.Printf("Creating output file %q.", outputPath)
 | 
						log.Printf("Creating output file %q.", outputPath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return exportJPEGSilent(stitchedImage, outputPath)
 | 
						// If there is a progress bar, start a goroutine that regularly updates it.
 | 
				
			||||||
 | 
						// We will base the progress on the number of pixels read from the stitched image.
 | 
				
			||||||
 | 
						if bar != nil {
 | 
				
			||||||
 | 
							_, max := stitchedImage.Progress()
 | 
				
			||||||
 | 
							bar.SetRefreshRate(250 * time.Millisecond).SetTotal(int64(max)).Start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							done := make(chan struct{})
 | 
				
			||||||
 | 
							defer func() {
 | 
				
			||||||
 | 
								done <- struct{}{}
 | 
				
			||||||
 | 
								bar.SetCurrent(bar.Total()).Finish()
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							go func() {
 | 
				
			||||||
 | 
								ticker := time.NewTicker(250 * time.Millisecond)
 | 
				
			||||||
 | 
								for {
 | 
				
			||||||
 | 
									select {
 | 
				
			||||||
 | 
									case <-done:
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									case <-ticker.C:
 | 
				
			||||||
 | 
										value, max := stitchedImage.Progress()
 | 
				
			||||||
 | 
										bar.SetCurrent(int64(value)).SetTotal(int64(max))
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return exportJPEG(stitchedImage, outputPath)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func exportJPEGSilent(stitchedImage image.Image, outputPath string) error {
 | 
					func exportJPEG(img image.Image, outputPath string) error {
 | 
				
			||||||
	f, err := os.Create(outputPath)
 | 
						f, err := os.Create(outputPath)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return fmt.Errorf("failed to create file: %w", err)
 | 
							return fmt.Errorf("failed to create file: %w", err)
 | 
				
			||||||
@ -30,7 +59,7 @@ func exportJPEGSilent(stitchedImage image.Image, outputPath string) error {
 | 
				
			|||||||
		Quality: 80,
 | 
							Quality: 80,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := jpeg.Encode(f, stitchedImage, options); err != nil {
 | 
						if err := jpeg.Encode(f, img, options); err != nil {
 | 
				
			||||||
		return fmt.Errorf("failed to encode image %q: %w", outputPath, err)
 | 
							return fmt.Errorf("failed to encode image %q: %w", outputPath, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
// Copyright (c) 2023 David Vogel
 | 
					// Copyright (c) 2023-2024 David Vogel
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// This software is released under the MIT License.
 | 
					// This software is released under the MIT License.
 | 
				
			||||||
// https://opensource.org/licenses/MIT
 | 
					// https://opensource.org/licenses/MIT
 | 
				
			||||||
@ -11,15 +11,44 @@ import (
 | 
				
			|||||||
	"image/png"
 | 
						"image/png"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/cheggaaa/pb/v3"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func exportPNG(stitchedImage image.Image, outputPath string) error {
 | 
					func exportPNGStitchedImage(stitchedImage *StitchedImage, outputPath string, bar *pb.ProgressBar) error {
 | 
				
			||||||
	log.Printf("Creating output file %q.", outputPath)
 | 
						log.Printf("Creating output file %q.", outputPath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return exportPNGSilent(stitchedImage, outputPath)
 | 
						// If there is a progress bar, start a goroutine that regularly updates it.
 | 
				
			||||||
 | 
						// We will base the progress on the number of pixels read from the stitched image.
 | 
				
			||||||
 | 
						if bar != nil {
 | 
				
			||||||
 | 
							_, max := stitchedImage.Progress()
 | 
				
			||||||
 | 
							bar.SetRefreshRate(250 * time.Millisecond).SetTotal(int64(max)).Start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							done := make(chan struct{})
 | 
				
			||||||
 | 
							defer func() {
 | 
				
			||||||
 | 
								done <- struct{}{}
 | 
				
			||||||
 | 
								bar.SetCurrent(bar.Total()).Finish()
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							go func() {
 | 
				
			||||||
 | 
								ticker := time.NewTicker(250 * time.Millisecond)
 | 
				
			||||||
 | 
								for {
 | 
				
			||||||
 | 
									select {
 | 
				
			||||||
 | 
									case <-done:
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									case <-ticker.C:
 | 
				
			||||||
 | 
										value, max := stitchedImage.Progress()
 | 
				
			||||||
 | 
										bar.SetCurrent(int64(value)).SetTotal(int64(max))
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return exportPNG(stitchedImage, outputPath)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func exportPNGSilent(stitchedImage image.Image, outputPath string) error {
 | 
					func exportPNG(img image.Image, outputPath string) error {
 | 
				
			||||||
	f, err := os.Create(outputPath)
 | 
						f, err := os.Create(outputPath)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return fmt.Errorf("failed to create file: %w", err)
 | 
							return fmt.Errorf("failed to create file: %w", err)
 | 
				
			||||||
@ -30,7 +59,7 @@ func exportPNGSilent(stitchedImage image.Image, outputPath string) error {
 | 
				
			|||||||
		CompressionLevel: png.DefaultCompression,
 | 
							CompressionLevel: png.DefaultCompression,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := encoder.Encode(f, stitchedImage); err != nil {
 | 
						if err := encoder.Encode(f, img); err != nil {
 | 
				
			||||||
		return fmt.Errorf("failed to encode image %q: %w", outputPath, err)
 | 
							return fmt.Errorf("failed to encode image %q: %w", outputPath, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -10,18 +10,46 @@ import (
 | 
				
			|||||||
	"image"
 | 
						"image"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/chai2010/webp"
 | 
						"github.com/Dadido3/go-libwebp/webp"
 | 
				
			||||||
 | 
						"github.com/cheggaaa/pb/v3"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func exportWebP(stitchedImage image.Image, outputPath string) error {
 | 
					func exportWebPStitchedImage(stitchedImage *StitchedImage, outputPath string, bar *pb.ProgressBar, webPLevel int) error {
 | 
				
			||||||
	log.Printf("Creating output file %q.", outputPath)
 | 
						log.Printf("Creating output file %q.", outputPath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return exportWebPSilent(stitchedImage, outputPath)
 | 
						// If there is a progress bar, start a goroutine that regularly updates it.
 | 
				
			||||||
 | 
						// We will base the progress on the number of pixels read from the stitched image.
 | 
				
			||||||
 | 
						if bar != nil {
 | 
				
			||||||
 | 
							_, max := stitchedImage.Progress()
 | 
				
			||||||
 | 
							bar.SetRefreshRate(250 * time.Millisecond).SetTotal(int64(max)).Start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							done := make(chan struct{})
 | 
				
			||||||
 | 
							defer func() {
 | 
				
			||||||
 | 
								done <- struct{}{}
 | 
				
			||||||
 | 
								bar.SetCurrent(bar.Total()).Finish()
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							go func() {
 | 
				
			||||||
 | 
								ticker := time.NewTicker(250 * time.Millisecond)
 | 
				
			||||||
 | 
								for {
 | 
				
			||||||
 | 
									select {
 | 
				
			||||||
 | 
									case <-done:
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									case <-ticker.C:
 | 
				
			||||||
 | 
										value, max := stitchedImage.Progress()
 | 
				
			||||||
 | 
										bar.SetCurrent(int64(value)).SetTotal(int64(max))
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return exportWebP(stitchedImage, outputPath, webPLevel)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func exportWebPSilent(stitchedImage image.Image, outputPath string) error {
 | 
					func exportWebP(img image.Image, outputPath string, webPLevel int) error {
 | 
				
			||||||
	bounds := stitchedImage.Bounds()
 | 
						bounds := img.Bounds()
 | 
				
			||||||
	if bounds.Dx() > 16383 || bounds.Dy() > 16383 {
 | 
						if bounds.Dx() > 16383 || bounds.Dy() > 16383 {
 | 
				
			||||||
		return fmt.Errorf("image size exceeds the maximum allowed size (16383) of a WebP image: %d x %d", bounds.Dx(), bounds.Dy())
 | 
							return fmt.Errorf("image size exceeds the maximum allowed size (16383) of a WebP image: %d x %d", bounds.Dx(), bounds.Dy())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -32,7 +60,12 @@ func exportWebPSilent(stitchedImage image.Image, outputPath string) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	defer f.Close()
 | 
						defer f.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err = webp.Encode(f, stitchedImage, &webp.Options{Lossless: true}); err != nil {
 | 
						webPConfig, err := webp.ConfigLosslessPreset(webPLevel)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("failed to create webP config: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = webp.Encode(f, img, webPConfig); err != nil {
 | 
				
			||||||
		return fmt.Errorf("failed to encode image %q: %w", outputPath, err)
 | 
							return fmt.Errorf("failed to encode image %q: %w", outputPath, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -12,7 +12,6 @@ import (
 | 
				
			|||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"sync"
 | 
					 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/1lann/promptui"
 | 
						"github.com/1lann/promptui"
 | 
				
			||||||
@ -27,6 +26,7 @@ var flagScaleDivider = flag.Int("divide", 1, "A downscaling factor. 2 will produ
 | 
				
			|||||||
var flagBlendTileLimit = flag.Int("blend-tile-limit", 9, "Limits median blending to the n newest tiles by file modification time. If set to 0, all available tiles will be median blended.")
 | 
					var flagBlendTileLimit = flag.Int("blend-tile-limit", 9, "Limits median blending to the n newest tiles by file modification time. If set to 0, all available tiles will be median blended.")
 | 
				
			||||||
var flagDZITileSize = flag.Int("dzi-tile-size", 512, "The size of the resulting deep zoom image (DZI) tiles in pixels.")
 | 
					var flagDZITileSize = flag.Int("dzi-tile-size", 512, "The size of the resulting deep zoom image (DZI) tiles in pixels.")
 | 
				
			||||||
var flagDZIOverlap = flag.Int("dzi-tile-overlap", 2, "The number of additional pixels around every deep zoom image (DZI) tile.")
 | 
					var flagDZIOverlap = flag.Int("dzi-tile-overlap", 2, "The number of additional pixels around every deep zoom image (DZI) tile.")
 | 
				
			||||||
 | 
					var flagWebPLevel = flag.Int("wepb-level", 8, "Compression level of WebP files, from 0 (fast) to 9 (slow, best compression).")
 | 
				
			||||||
var flagXMin = flag.Int("xmin", 0, "Left bound of the output rectangle. This coordinate is included in the output.")
 | 
					var flagXMin = flag.Int("xmin", 0, "Left bound of the output rectangle. This coordinate is included in the output.")
 | 
				
			||||||
var flagYMin = flag.Int("ymin", 0, "Upper bound of the output rectangle. This coordinate is included in the output.")
 | 
					var flagYMin = flag.Int("ymin", 0, "Upper bound of the output rectangle. This coordinate is included in the output.")
 | 
				
			||||||
var flagXMax = flag.Int("xmax", 0, "Right bound of the output rectangle. This coordinate is not included in the output.")
 | 
					var flagXMax = flag.Int("xmax", 0, "Right bound of the output rectangle. This coordinate is not included in the output.")
 | 
				
			||||||
@ -287,11 +287,35 @@ func main() {
 | 
				
			|||||||
		fmt.Sscanf(result, "%d", flagDZIOverlap)
 | 
							fmt.Sscanf(result, "%d", flagDZIOverlap)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	startTime := time.Now()
 | 
						// Query the user, if there were no cmd arguments given.
 | 
				
			||||||
 | 
						if flag.NFlag() == 0 && (fileExtension == ".dzi" || fileExtension == ".webp") {
 | 
				
			||||||
 | 
							prompt := promptui.Prompt{
 | 
				
			||||||
 | 
								Label:     "Enter WebP compression level:",
 | 
				
			||||||
 | 
								Default:   fmt.Sprint(*flagWebPLevel),
 | 
				
			||||||
 | 
								AllowEdit: true,
 | 
				
			||||||
 | 
								Validate: func(s string) error {
 | 
				
			||||||
 | 
									var num int
 | 
				
			||||||
 | 
									_, err := fmt.Sscanf(s, "%d", &num)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if int(num) < 0 {
 | 
				
			||||||
 | 
										return fmt.Errorf("level must be at least 0")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if int(num) > 9 {
 | 
				
			||||||
 | 
										return fmt.Errorf("level must not be larger than 9")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bar := pb.Full.New(0)
 | 
									return nil
 | 
				
			||||||
	var wg sync.WaitGroup
 | 
								},
 | 
				
			||||||
	done := make(chan struct{})
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							result, err := prompt.Run()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Panicf("Error while getting user input: %v.", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							fmt.Sscanf(result, "%d", flagWebPLevel)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	blendMethod := BlendMethodMedian{
 | 
						blendMethod := BlendMethodMedian{
 | 
				
			||||||
		BlendTileLimit: *flagBlendTileLimit, // Limit median blending to the n newest tiles by file modification time.
 | 
							BlendTileLimit: *flagBlendTileLimit, // Limit median blending to the n newest tiles by file modification time.
 | 
				
			||||||
@ -301,53 +325,31 @@ func main() {
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Panicf("NewStitchedImage() failed: %v.", err)
 | 
							log.Panicf("NewStitchedImage() failed: %v.", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_, max := stitchedImage.Progress()
 | 
					 | 
				
			||||||
	bar.SetTotal(int64(max)).Start().SetRefreshRate(250 * time.Millisecond)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Query progress and draw progress bar.
 | 
						bar := pb.Full.New(0)
 | 
				
			||||||
	wg.Add(1)
 | 
					 | 
				
			||||||
	go func() {
 | 
					 | 
				
			||||||
		defer wg.Done()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		ticker := time.NewTicker(250 * time.Millisecond)
 | 
					 | 
				
			||||||
		for {
 | 
					 | 
				
			||||||
			select {
 | 
					 | 
				
			||||||
			case <-done:
 | 
					 | 
				
			||||||
				value, _ := stitchedImage.Progress()
 | 
					 | 
				
			||||||
				bar.SetCurrent(int64(value))
 | 
					 | 
				
			||||||
				bar.Finish()
 | 
					 | 
				
			||||||
				return
 | 
					 | 
				
			||||||
			case <-ticker.C:
 | 
					 | 
				
			||||||
				value, _ := stitchedImage.Progress()
 | 
					 | 
				
			||||||
				bar.SetCurrent(int64(value))
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch fileExtension {
 | 
						switch fileExtension {
 | 
				
			||||||
	case ".png":
 | 
						case ".png":
 | 
				
			||||||
		if err := exportPNG(stitchedImage, *flagOutputPath); err != nil {
 | 
							if err := exportPNGStitchedImage(stitchedImage, *flagOutputPath, bar); err != nil {
 | 
				
			||||||
			log.Panicf("Export of PNG file failed: %v", err)
 | 
								log.Panicf("Export of PNG file failed: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	case ".jpg", ".jpeg":
 | 
						case ".jpg", ".jpeg":
 | 
				
			||||||
		if err := exportJPEG(stitchedImage, *flagOutputPath); err != nil {
 | 
							if err := exportJPEGStitchedImage(stitchedImage, *flagOutputPath, bar); err != nil {
 | 
				
			||||||
			log.Panicf("Export of JPEG file failed: %v", err)
 | 
								log.Panicf("Export of JPEG file failed: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	case ".webp":
 | 
						case ".webp":
 | 
				
			||||||
		if err := exportWebP(stitchedImage, *flagOutputPath); err != nil {
 | 
							if err := exportWebPStitchedImage(stitchedImage, *flagOutputPath, bar, *flagWebPLevel); err != nil {
 | 
				
			||||||
			log.Panicf("Export of WebP file failed: %v", err)
 | 
								log.Panicf("Export of WebP file failed: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	case ".dzi":
 | 
						case ".dzi":
 | 
				
			||||||
		if err := exportDZI(stitchedImage, *flagOutputPath, *flagDZITileSize, *flagDZIOverlap); err != nil {
 | 
							if err := exportDZIStitchedImage(stitchedImage, *flagOutputPath, bar, *flagDZITileSize, *flagDZIOverlap, *flagWebPLevel); err != nil {
 | 
				
			||||||
			log.Panicf("Export of DZI file failed: %v", err)
 | 
								log.Panicf("Export of DZI file failed: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		log.Panicf("Unknown output format %q.", fileExtension)
 | 
							log.Panicf("Unknown output format %q.", fileExtension)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	done <- struct{}{}
 | 
						log.Printf("Created output in %v.", time.Since(bar.StartTime()))
 | 
				
			||||||
	wg.Wait()
 | 
					 | 
				
			||||||
	log.Printf("Created output in %v.", time.Since(startTime))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	//fmt.Println("Press the enter key to terminate the console screen!")
 | 
						//fmt.Println("Press the enter key to terminate the console screen!")
 | 
				
			||||||
	//fmt.Scanln()
 | 
						//fmt.Scanln()
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
// Copyright (c) 2019-2022 David Vogel
 | 
					// Copyright (c) 2019-2024 David Vogel
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// This software is released under the MIT License.
 | 
					// This software is released under the MIT License.
 | 
				
			||||||
// https://opensource.org/licenses/MIT
 | 
					// https://opensource.org/licenses/MIT
 | 
				
			||||||
@ -9,6 +9,7 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"image"
 | 
						"image"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// QuickSelect returns the kth smallest element of the given unsorted list.
 | 
					// QuickSelect returns the kth smallest element of the given unsorted list.
 | 
				
			||||||
@ -96,3 +97,44 @@ func DivideCeil(a, b int) int {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return temp
 | 
						return temp
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// https://gist.github.com/cstockton/d611ced26bb6b4d3f7d4237abb8613c4
 | 
				
			||||||
 | 
					type LimitGroup struct {
 | 
				
			||||||
 | 
						wg   sync.WaitGroup
 | 
				
			||||||
 | 
						mu   *sync.Mutex
 | 
				
			||||||
 | 
						c    *sync.Cond
 | 
				
			||||||
 | 
						l, n int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewLimitGroup(n int) *LimitGroup {
 | 
				
			||||||
 | 
						mu := new(sync.Mutex)
 | 
				
			||||||
 | 
						return &LimitGroup{
 | 
				
			||||||
 | 
							mu: mu,
 | 
				
			||||||
 | 
							c:  sync.NewCond(mu),
 | 
				
			||||||
 | 
							l:  n,
 | 
				
			||||||
 | 
							n:  n,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (lg *LimitGroup) Add(delta int) {
 | 
				
			||||||
 | 
						lg.mu.Lock()
 | 
				
			||||||
 | 
						defer lg.mu.Unlock()
 | 
				
			||||||
 | 
						if delta > lg.l {
 | 
				
			||||||
 | 
							panic(`LimitGroup: delta must not exceed limit`)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for lg.n < 1 {
 | 
				
			||||||
 | 
							lg.c.Wait()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						lg.n -= delta
 | 
				
			||||||
 | 
						lg.wg.Add(delta)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (lg *LimitGroup) Done() {
 | 
				
			||||||
 | 
						lg.mu.Lock()
 | 
				
			||||||
 | 
						defer lg.mu.Unlock()
 | 
				
			||||||
 | 
						lg.n++
 | 
				
			||||||
 | 
						lg.c.Signal()
 | 
				
			||||||
 | 
						lg.wg.Done()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (lg *LimitGroup) Wait() { lg.wg.Wait() }
 | 
				
			||||||
 | 
				
			|||||||
@ -198,6 +198,16 @@ function Modification.SetMemoryOptions(memory)
 | 
				
			|||||||
					mPlayerNeverDies = function(value) ffi.cast("char*", 0x01305862)[0] = value end,
 | 
										mPlayerNeverDies = function(value) ffi.cast("char*", 0x01305862)[0] = value end,
 | 
				
			||||||
					mFreezeAI = function(value) ffi.cast("char*", 0x01305863)[0] = value end,
 | 
										mFreezeAI = function(value) ffi.cast("char*", 0x01305863)[0] = value end,
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 | 
									{_Offset = 0x01173F34, _BuildString = "Build Feb  6 2024 15:54:02", -- Steam dev build.
 | 
				
			||||||
 | 
										mPostFxDisabled = function(value) ffi.cast("char*", 0x0130982C+0)[0] = value end,
 | 
				
			||||||
 | 
										mGuiDisabled = function(value) ffi.cast("char*", 0x0130982C+1)[0] = value end,
 | 
				
			||||||
 | 
										mGuiHalfSize = function(value) ffi.cast("char*", 0x0130982C+2)[0] = value end,
 | 
				
			||||||
 | 
										mFogOfWarOpenEverywhere = function(value) ffi.cast("char*", 0x0130982C+3)[0] = value end,
 | 
				
			||||||
 | 
										mTrailerMode = function(value) ffi.cast("char*", 0x0130982C+4)[0] = value end,
 | 
				
			||||||
 | 
										mDayTimeRotationPause = function(value) ffi.cast("char*", 0x0130982C+5)[0] = value end,
 | 
				
			||||||
 | 
										mPlayerNeverDies = function(value) ffi.cast("char*", 0x0130982C+6)[0] = value end,
 | 
				
			||||||
 | 
										mFreezeAI = function(value) ffi.cast("char*", 0x0130982C+7)[0] = value end,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		[false] = {
 | 
							[false] = {
 | 
				
			||||||
@ -265,6 +275,13 @@ function Modification.SetMemoryOptions(memory)
 | 
				
			|||||||
						ptr[0] = value -- This basically just changes the value that Noita forces to the "mods_have_been_active_during_this_run" member of the WorldStateComponent when any mod is enabled.
 | 
											ptr[0] = value -- This basically just changes the value that Noita forces to the "mods_have_been_active_during_this_run" member of the WorldStateComponent when any mod is enabled.
 | 
				
			||||||
					end,
 | 
										end,
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 | 
									{_Offset = 0x00FEEFC0, _BuildString = "Build Feb  6 2024 15:58:22", -- Steam build.
 | 
				
			||||||
 | 
										enableModDetection = function(value)
 | 
				
			||||||
 | 
											local ptr = ffi.cast("char*", 0x006AD611+6)
 | 
				
			||||||
 | 
											Memory.VirtualProtect(ptr, 1, Memory.PAGE_EXECUTE_READWRITE)
 | 
				
			||||||
 | 
											ptr[0] = value -- This basically just changes the value that Noita forces to the "mods_have_been_active_during_this_run" member of the WorldStateComponent when any mod is enabled.
 | 
				
			||||||
 | 
										end,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -343,6 +360,9 @@ function Modification.RequiredChanges()
 | 
				
			|||||||
		magic["GRID_RENDER_BORDER"] = "3" -- This will widen the right side of the virtual rectangle. It also shifts the world coordinates to the right.
 | 
							magic["GRID_RENDER_BORDER"] = "3" -- This will widen the right side of the virtual rectangle. It also shifts the world coordinates to the right.
 | 
				
			||||||
		magic["VIRTUAL_RESOLUTION_OFFSET_X"] = "-3"
 | 
							magic["VIRTUAL_RESOLUTION_OFFSET_X"] = "-3"
 | 
				
			||||||
		magic["VIRTUAL_RESOLUTION_OFFSET_Y"] = "0"
 | 
							magic["VIRTUAL_RESOLUTION_OFFSET_Y"] = "0"
 | 
				
			||||||
 | 
							magic["STREAMING_CHUNK_TARGET"] = "16" -- Keep more chunks alive.
 | 
				
			||||||
 | 
							magic["GRID_MAX_UPDATES_PER_FRAME"] = "1024" -- Allow more pixel physics simulation steps (in 32x32 regions) per frame. With too few, objects can glitch through the terrain/explode.
 | 
				
			||||||
 | 
							magic["GRID_MIN_UPDATES_PER_FRAME"] = "0" -- Also allow no updates.
 | 
				
			||||||
	else
 | 
						else
 | 
				
			||||||
		-- Reset some values if there is no custom resolution requested.
 | 
							-- Reset some values if there is no custom resolution requested.
 | 
				
			||||||
		config["internal_size_w"] = "1280"
 | 
							config["internal_size_w"] = "1280"
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							@ -1,10 +1,10 @@
 | 
				
			|||||||
module github.com/Dadido3/noita-mapcap
 | 
					module github.com/Dadido3/noita-mapcap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
go 1.21
 | 
					go 1.22
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/1lann/promptui v0.8.1-0.20220708222609-81fad96dd5e1
 | 
						github.com/1lann/promptui v0.8.1-0.20220708222609-81fad96dd5e1
 | 
				
			||||||
	github.com/chai2010/webp v1.1.1
 | 
						github.com/Dadido3/go-libwebp v0.3.0
 | 
				
			||||||
	github.com/cheggaaa/pb/v3 v3.1.4
 | 
						github.com/cheggaaa/pb/v3 v3.1.4
 | 
				
			||||||
	github.com/coreos/go-semver v0.3.1
 | 
						github.com/coreos/go-semver v0.3.1
 | 
				
			||||||
	github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237
 | 
						github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										8
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.sum
									
									
									
									
									
								
							@ -4,6 +4,12 @@ github.com/1lann/promptui v0.8.1-0.20220708222609-81fad96dd5e1 h1:LejjvYg4tCW5HO
 | 
				
			|||||||
github.com/1lann/promptui v0.8.1-0.20220708222609-81fad96dd5e1/go.mod h1:cnC/60IoLiDM0GhdKYJ6oO7AwpZe1IQfPnSKlAURgHw=
 | 
					github.com/1lann/promptui v0.8.1-0.20220708222609-81fad96dd5e1/go.mod h1:cnC/60IoLiDM0GhdKYJ6oO7AwpZe1IQfPnSKlAURgHw=
 | 
				
			||||||
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f h1:l7moT9o/v/9acCWA64Yz/HDLqjcRTvc0noQACi4MsJw=
 | 
					github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f h1:l7moT9o/v/9acCWA64Yz/HDLqjcRTvc0noQACi4MsJw=
 | 
				
			||||||
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f/go.mod h1:vIOkSdX3NDCPwgu8FIuTat2zDF0FPXXQ0RYFRy+oQic=
 | 
					github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f/go.mod h1:vIOkSdX3NDCPwgu8FIuTat2zDF0FPXXQ0RYFRy+oQic=
 | 
				
			||||||
 | 
					github.com/Dadido3/go-libwebp v0.1.0 h1:aMM8wwOSyq8mk/xL9ze3vQHPygDU8TMXKH0TViwkBLE=
 | 
				
			||||||
 | 
					github.com/Dadido3/go-libwebp v0.1.0/go.mod h1:rYiWwlI58XRSMUFMw23nMezErbjX3Z5Xv0Kk3w6Mwwo=
 | 
				
			||||||
 | 
					github.com/Dadido3/go-libwebp v0.2.0 h1:SmssjSkrDkwSOGEpdultvneADGYaEqPbv6FcgausaK0=
 | 
				
			||||||
 | 
					github.com/Dadido3/go-libwebp v0.2.0/go.mod h1:rYiWwlI58XRSMUFMw23nMezErbjX3Z5Xv0Kk3w6Mwwo=
 | 
				
			||||||
 | 
					github.com/Dadido3/go-libwebp v0.3.0 h1:Qr3Gt8Kn4qgemezDVnjAJffMB9C0QJhxP+9u0U5mC94=
 | 
				
			||||||
 | 
					github.com/Dadido3/go-libwebp v0.3.0/go.mod h1:rYiWwlI58XRSMUFMw23nMezErbjX3Z5Xv0Kk3w6Mwwo=
 | 
				
			||||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
 | 
					github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
 | 
				
			||||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
 | 
					github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
 | 
				
			||||||
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
 | 
					github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
 | 
				
			||||||
@ -16,8 +22,6 @@ github.com/benoitkugler/textprocessing v0.0.3 h1:Q2X+Z6vxuW5Bxn1R9RaNt0qcprBfpc2
 | 
				
			|||||||
github.com/benoitkugler/textprocessing v0.0.3/go.mod h1:/4bLyCf1QYywunMK3Gf89Nhb50YI/9POewqrLxWhxd4=
 | 
					github.com/benoitkugler/textprocessing v0.0.3/go.mod h1:/4bLyCf1QYywunMK3Gf89Nhb50YI/9POewqrLxWhxd4=
 | 
				
			||||||
github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY=
 | 
					github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY=
 | 
				
			||||||
github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=
 | 
					github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=
 | 
				
			||||||
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
 | 
					 | 
				
			||||||
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
 | 
					 | 
				
			||||||
github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo=
 | 
					github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo=
 | 
				
			||||||
github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA=
 | 
					github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA=
 | 
				
			||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 | 
					github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user