mirror of
https://github.com/Dadido3/noita-mapcap.git
synced 2024-12-22 02:17:33 +00:00
Several updates
- Change virtual resolution to 1280x720 - Change virtual to screen pixel ratio to 1:1 - Increase grid size to 420 - Make the mod capture only the window (and only the client area) - Increase STREAMING_CHUNK_TARGET, so that chunks don't unload after some frames - Add ingame warnings for wrong settings, and information how to fix these - Update README.md
This commit is contained in:
parent
638f6223c3
commit
c87a4d05d0
13
README.md
13
README.md
@ -2,7 +2,7 @@
|
||||
|
||||
Addon that captures the map and saves it as image.
|
||||
|
||||
![](images/example1.png)
|
||||
![missing image](images/example1.png)
|
||||
|
||||
A resulting image with close to 3 gigapixels can be [seen here](https://easyzoom.com/image/158284/album/0/4) (Warning: Spoilers).
|
||||
|
||||
@ -20,10 +20,14 @@ A resulting image with close to 3 gigapixels can be [seen here](https://easyzoom
|
||||
1. Have Noita installed.
|
||||
2. Download the [latest release of the mod from this link](https://github.com/Dadido3/noita-mapcap/releases/latest) (The `Windows.x86.7z`, not the source)
|
||||
3. Unpack it into your mods folder, so that you get the following file structure `.../Noita/mods/noita-mapcap/mod.xml`.
|
||||
4. Set your resolution to 1920x1080 if possible, and use the `Windowed` mode. (Not `Fullscreen (Windowed)`!) If you have to use a different resolution, see advanced usage.
|
||||
4. Set your resolution to 1280x720, and use the `Windowed` mode. (Not `Fullscreen (Windowed)`!) If you have to use a different resolution, see advanced usage.
|
||||
5. Enable the mod and restart Noita.
|
||||
6. In the game you should see a `>> Start capturing map <<` text on the screen, click it.
|
||||
7. The screen will jump around, and the game will take screenshots automatically. Don't interfere with it. Screenshots are saved in `.../Noita/mods/noita-mapcap/output/`.
|
||||
7. The screen will jump around, and the game will take screenshots automatically.
|
||||
- Screenshots are saved in `.../Noita/mods/noita-mapcap/output/`.
|
||||
- Don't cover the game window.
|
||||
- Don't move the game window outside of screen space.
|
||||
- If you need to pause, use the ESC menu.
|
||||
8. When you think you are done, close noita.
|
||||
9. Start `.../Noita/mods/noita-mapcap/bin/stitch/stitch.exe`.
|
||||
- Use the default values to create a complete stitch.
|
||||
@ -44,9 +48,6 @@ The following two formulae have to be true:
|
||||
- `VIRTUAL_RESOLUTION_*` can be found inside `.../Noita/mods/noita-mapcap/files/magic_numbers.xml`
|
||||
- and `SCREEN_RESOLUTION_*` is the screen resolution you have set up in noita.
|
||||
|
||||
If you have a resolution of `1366 x 768`, then you should change the `VIRTUAL_RESOLUTION_*` to `683 x 384`.
|
||||
Another solution would be to change the `CAPTURE_PIXEL_SIZE` to `1.423`, but then you would get blurry images.
|
||||
|
||||
You can also change how much the tiles overlap by adjusting the `CAPTURE_GRID_SIZE` in `.../Noita/mods/noita-mapcap/files/capture.lua`. If you increase the grid size, you can capture more area per time. But on the other hand the stitcher may not be able to remove artifacts if the tiles don't overlap enough.
|
||||
|
||||
## License
|
||||
|
@ -8,104 +8,153 @@ UsePNGImageEncoder()
|
||||
Declare Worker(*Dummy)
|
||||
|
||||
Structure QueueElement
|
||||
img.i
|
||||
x.i
|
||||
y.i
|
||||
img.i
|
||||
x.i
|
||||
y.i
|
||||
EndStructure
|
||||
|
||||
; Source: https://www.purebasic.fr/english/viewtopic.php?f=13&t=29981&start=15
|
||||
Procedure EnumWindowsProc(hWnd.l, *lParam.Long)
|
||||
Protected lpProc.l
|
||||
GetWindowThreadProcessId_(hWnd, @lpProc)
|
||||
If *lParam\l = lpProc ; Check if current window's processID matches
|
||||
*lParam\l = hWnd ; Replace processID in the param With the hwnd As result
|
||||
ProcedureReturn #False ; Return false to stop iterating
|
||||
EndIf
|
||||
ProcedureReturn #True
|
||||
EndProcedure
|
||||
|
||||
; Source: https://www.purebasic.fr/english/viewtopic.php?f=13&t=29981&start=15
|
||||
; Returns the first window associated with the given process handle
|
||||
Procedure GetProcHwnd()
|
||||
Protected pID.l = GetCurrentProcessId_()
|
||||
Protected tempParam.l = pID
|
||||
EnumWindows_(@EnumWindowsProc(), @tempParam)
|
||||
If tempParam = pID ; Check if anything was found
|
||||
ProcedureReturn #Null
|
||||
EndIf
|
||||
ProcedureReturn tempParam ; This is a valid hWnd at this point
|
||||
EndProcedure
|
||||
|
||||
; Get the client rectangle of the "Main" window of this process in screen coordinates
|
||||
ProcedureDLL GetRect(*rect.RECT)
|
||||
Protected hWnd.l = GetProcHwnd()
|
||||
If Not hWnd
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
If Not *rect
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
|
||||
GetClientRect_(hWnd, *rect)
|
||||
|
||||
; A RECT consists basically of two POINT structures
|
||||
ClientToScreen_(hWnd, @*rect\left)
|
||||
ClientToScreen_(hWnd, @*rect\Right)
|
||||
|
||||
ProcedureReturn #True
|
||||
EndProcedure
|
||||
|
||||
ProcedureDLL AttachProcess(Instance)
|
||||
Global Semaphore = CreateSemaphore()
|
||||
Global Mutex = CreateMutex()
|
||||
Global NewList Queue.QueueElement()
|
||||
Global Semaphore = CreateSemaphore()
|
||||
Global Mutex = CreateMutex()
|
||||
Global NewList Queue.QueueElement()
|
||||
|
||||
ExamineDesktops()
|
||||
CreateDirectory("mods/noita-mapcap/output/")
|
||||
CreateDirectory("mods/noita-mapcap/output/")
|
||||
|
||||
For i = 1 To 4
|
||||
CreateThread(@Worker(), #Null)
|
||||
Next
|
||||
For i = 1 To 4
|
||||
CreateThread(@Worker(), #Null)
|
||||
Next
|
||||
EndProcedure
|
||||
|
||||
Procedure Worker(*Dummy)
|
||||
Protected img, x, y
|
||||
Protected img, x, y
|
||||
|
||||
Repeat
|
||||
WaitSemaphore(Semaphore)
|
||||
Repeat
|
||||
WaitSemaphore(Semaphore)
|
||||
|
||||
LockMutex(Mutex)
|
||||
FirstElement(Queue())
|
||||
img = Queue()\img
|
||||
x = Queue()\x
|
||||
y = Queue()\y
|
||||
DeleteElement(Queue())
|
||||
UnlockMutex(Mutex)
|
||||
LockMutex(Mutex)
|
||||
FirstElement(Queue())
|
||||
img = Queue()\img
|
||||
x = Queue()\x
|
||||
y = Queue()\y
|
||||
DeleteElement(Queue())
|
||||
UnlockMutex(Mutex)
|
||||
|
||||
SaveImage(img, "mods/noita-mapcap/output/" + x + "," + y + ".png", #PB_ImagePlugin_PNG)
|
||||
FreeImage(img)
|
||||
ForEver
|
||||
SaveImage(img, "mods/noita-mapcap/output/" + x + "," + y + ".png", #PB_ImagePlugin_PNG)
|
||||
;SaveImage(img, "" + x + "," + y + ".png", #PB_ImagePlugin_PNG) ; Test
|
||||
|
||||
FreeImage(img)
|
||||
ForEver
|
||||
EndProcedure
|
||||
|
||||
ProcedureDLL Capture(px.i, py.i)
|
||||
; Get dimensions of main screen
|
||||
Protected rect.RECT
|
||||
|
||||
x = DesktopX(0)
|
||||
y = DesktopY(0)
|
||||
w = DesktopWidth(0)
|
||||
h = DesktopHeight(0)
|
||||
If Not GetRect(@rect)
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
|
||||
imageID = CreateImage(#PB_Any, w, h)
|
||||
If Not imageID
|
||||
ProcedureReturn
|
||||
EndIf
|
||||
imageID = CreateImage(#PB_Any, rect\right-rect\left, rect\bottom-rect\top)
|
||||
If Not imageID
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
|
||||
; Get DC of whole screen
|
||||
screenDC = GetDC_(#Null)
|
||||
If Not screenDC
|
||||
FreeImage(imageID)
|
||||
ProcedureReturn
|
||||
EndIf
|
||||
; Get DC of whole screen
|
||||
screenDC = GetDC_(#Null)
|
||||
If Not screenDC
|
||||
FreeImage(imageID)
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
|
||||
hDC = StartDrawing(ImageOutput(imageID))
|
||||
If Not hDC
|
||||
FreeImage(imageID)
|
||||
ReleaseDC_(#Null, screenDC)
|
||||
ProcedureReturn
|
||||
EndIf
|
||||
If Not BitBlt_(hDC, 0, 0, w, h, screenDC, x, y, #SRCCOPY) ; After some time BitBlt will fail, no idea why. Also, that's moments before noita crashes.
|
||||
FreeImage(imageID)
|
||||
ReleaseDC_(#Null, screenDC)
|
||||
StopDrawing()
|
||||
ProcedureReturn
|
||||
EndIf
|
||||
StopDrawing()
|
||||
hDC = StartDrawing(ImageOutput(imageID))
|
||||
If Not hDC
|
||||
FreeImage(imageID)
|
||||
ReleaseDC_(#Null, screenDC)
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
If Not BitBlt_(hDC, 0, 0, rect\right-rect\left, rect\bottom-rect\top, screenDC, rect\left, rect\top, #SRCCOPY) ; After some time BitBlt will fail, no idea why. Also, that's moments before noita crashes.
|
||||
FreeImage(imageID)
|
||||
ReleaseDC_(#Null, screenDC)
|
||||
StopDrawing()
|
||||
ProcedureReturn #False
|
||||
EndIf
|
||||
StopDrawing()
|
||||
|
||||
ReleaseDC_(#Null, screenDC)
|
||||
ReleaseDC_(#Null, screenDC)
|
||||
|
||||
LockMutex(Mutex)
|
||||
; Check if the queue has too many elements, if so, wait. (Simulate go's channels)
|
||||
While ListSize(Queue()) > 0
|
||||
UnlockMutex(Mutex)
|
||||
Delay(10)
|
||||
LockMutex(Mutex)
|
||||
Wend
|
||||
LastElement(Queue())
|
||||
AddElement(Queue())
|
||||
Queue()\img = imageID
|
||||
Queue()\x = px
|
||||
Queue()\y = py
|
||||
UnlockMutex(Mutex)
|
||||
LockMutex(Mutex)
|
||||
; Check if the queue has too many elements, if so, wait. (Simulate go's channels)
|
||||
While ListSize(Queue()) > 0
|
||||
UnlockMutex(Mutex)
|
||||
Delay(10)
|
||||
LockMutex(Mutex)
|
||||
Wend
|
||||
LastElement(Queue())
|
||||
AddElement(Queue())
|
||||
Queue()\img = imageID
|
||||
Queue()\x = px
|
||||
Queue()\y = py
|
||||
UnlockMutex(Mutex)
|
||||
|
||||
SignalSemaphore(Semaphore)
|
||||
SignalSemaphore(Semaphore)
|
||||
|
||||
ProcedureReturn #True
|
||||
EndProcedure
|
||||
|
||||
; #### Test
|
||||
;AttachProcess(0)
|
||||
;OpenWindow(0, 100, 200, 195, 260, "PureBasic Window", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget)
|
||||
;Delay(1000)
|
||||
;Capture(123, 123)
|
||||
;Delay(1000)
|
||||
|
||||
; IDE Options = PureBasic 5.71 LTS (Windows - x64)
|
||||
; ExecutableFormat = Shared dll
|
||||
; CursorPosition = 25
|
||||
; FirstLine = 3
|
||||
; Folding = -
|
||||
; CursorPosition = 140
|
||||
; FirstLine = 94
|
||||
; Folding = --
|
||||
; EnableThread
|
||||
; EnableXP
|
||||
; Executable = capture.dll
|
||||
; DisableDebugger
|
||||
; Compiler = PureBasic 5.71 LTS (Windows - x86)
|
Binary file not shown.
@ -29,7 +29,7 @@ example list of files:
|
||||
- Run the program and follow the interactive prompt.
|
||||
- Run the program with parameters:
|
||||
- `divide int`
|
||||
A downscaling factor. 2 will produce an image with half the side lengths. (default 2)
|
||||
A downscaling factor. 2 will produce an image with half the side lengths. (default 1)
|
||||
- `input string`The source path of the image tiles to be stitched. (default "..\\..\\output")
|
||||
- `output string`
|
||||
The path and filename of the resulting stitched image. (default "output.png")
|
||||
|
@ -19,7 +19,7 @@ import (
|
||||
|
||||
var flagInputPath = flag.String("input", filepath.Join(".", "..", "..", "output"), "The source path of the image tiles to be stitched.")
|
||||
var flagOutputPath = flag.String("output", filepath.Join(".", "output.png"), "The path and filename of the resulting stitched image.")
|
||||
var flagScaleDivider = flag.Int("divide", 2, "A downscaling factor. 2 will produce an image with half the side lengths.")
|
||||
var flagScaleDivider = flag.Int("divide", 1, "A downscaling factor. 2 will produce an image with half the side lengths.")
|
||||
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 flagXMax = flag.Int("xmax", 0, "Right bound of the output rectangle. This coordinate is not included in the output.")
|
||||
|
@ -3,10 +3,10 @@
|
||||
-- This software is released under the MIT License.
|
||||
-- https://opensource.org/licenses/MIT
|
||||
|
||||
local CAPTURE_PIXEL_SIZE = 2 -- in FullHD an ingame pixel is expected to be 2 real pixels
|
||||
local CAPTURE_GRID_SIZE = 256 -- in ingame pixels. There will be 6 to 12 images overlapping
|
||||
local CAPTURE_DELAY = 15 -- in frames
|
||||
local CAPTURE_FORCE_HP = 4 -- * 25HP
|
||||
CAPTURE_PIXEL_SIZE = 1 -- Screen to virtual pixel ratio
|
||||
CAPTURE_GRID_SIZE = 420 -- in ingame pixels. There will always be 3 to 6 images overlapping
|
||||
CAPTURE_DELAY = 15 -- in frames
|
||||
CAPTURE_FORCE_HP = 4 -- * 25HP
|
||||
|
||||
local function preparePlayer()
|
||||
local playerEntity = getPlayer()
|
||||
|
@ -10,9 +10,28 @@ if not status then
|
||||
print("Error loading capture lib: " .. cap)
|
||||
end
|
||||
ffi.cdef [[
|
||||
void Capture(int x, int y);
|
||||
typedef long LONG;
|
||||
typedef struct {
|
||||
LONG left;
|
||||
LONG top;
|
||||
LONG right;
|
||||
LONG bottom;
|
||||
} RECT;
|
||||
|
||||
bool GetRect(RECT* rect);
|
||||
bool Capture(int x, int y);
|
||||
]]
|
||||
|
||||
function TriggerCapture(x, y)
|
||||
caplib.Capture(x, y)
|
||||
return caplib.Capture(x, y)
|
||||
end
|
||||
|
||||
-- Get the client rectangle of the "Main" window of this process in screen coordinates
|
||||
function GetRect()
|
||||
local rect = ffi.new("RECT", 0, 0, 0, 0)
|
||||
if not caplib.GetRect(rect) then
|
||||
return nil
|
||||
end
|
||||
|
||||
return rect
|
||||
end
|
||||
|
@ -1,12 +1,12 @@
|
||||
<MagicNumbers VIRTUAL_RESOLUTION_X="960"
|
||||
VIRTUAL_RESOLUTION_Y="540"
|
||||
CAMERA_NO_MOVE_BUFFER_NEAR_VIEWPORT_EDGE="0.0"
|
||||
CAMERA_MOUSE_INTERPOLATION_SPEED="0.0"
|
||||
CAMERA_POSITION_INTERPOLATION_SPEED="50.0"
|
||||
<MagicNumbers VIRTUAL_RESOLUTION_X="1280"
|
||||
VIRTUAL_RESOLUTION_Y="720"
|
||||
STREAMING_CHUNK_TARGET="24"
|
||||
DRAW_PARALLAX_BACKGROUND="0"
|
||||
DEBUG_FREE_CAMERA_SPEED="10"
|
||||
DEBUG_NO_LOGO_SPLASHES="1"
|
||||
DEBUG_PAUSE_GRID_UPDATE="1"
|
||||
DEBUG_PAUSE_BOX2D="1"
|
||||
DEBUG_DISABLE_POSTFX_DITHERING="1">
|
||||
DEBUG_DISABLE_POSTFX_DITHERING="1"
|
||||
DEBUG_NO_PAUSE_ON_WINDOW_FOCUS_LOST="1"
|
||||
DEBUG_LUA="1">
|
||||
</MagicNumbers>
|
||||
|
76
files/ui.lua
76
files/ui.lua
@ -13,9 +13,81 @@ async_loop(
|
||||
|
||||
GuiLayoutBeginVertical(modGUI, 50, 50)
|
||||
if not UiReduce then
|
||||
local problem
|
||||
local rect = GetRect()
|
||||
|
||||
if not rect then
|
||||
GuiTextCentered(modGUI, 0, 0, '!!! WARNING !!! You are not using "Windowed" mode.')
|
||||
GuiTextCentered(modGUI, 0, 0, "To fix the problem, do one of these:")
|
||||
GuiTextCentered(modGUI, 0, 0, '- Change the window mode in the game options to "Windowed"')
|
||||
GuiTextCentered(modGUI, 0, 0, " ")
|
||||
problem = true
|
||||
end
|
||||
|
||||
if rect then
|
||||
local screenWidth, screenHeight = rect.right - rect.left, rect.bottom - rect.top
|
||||
local virtualWidth, virtualHeight =
|
||||
tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_X")),
|
||||
tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_Y"))
|
||||
local ratioX, ratioY = screenWidth / virtualWidth, screenHeight / virtualHeight
|
||||
--GuiTextCentered(modGUI, 0, 0, string.format("SCREEN_RESOLUTION_*: %d, %d", screenWidth, screenHeight))
|
||||
--GuiTextCentered(modGUI, 0, 0, string.format("VIRTUAL_RESOLUTION_*: %d, %d", virtualWidth, virtualHeight))
|
||||
if math.abs(ratioX - CAPTURE_PIXEL_SIZE) > 0.0001 or math.abs(ratioY - CAPTURE_PIXEL_SIZE) > 0.0001 then
|
||||
GuiTextCentered(modGUI, 0, 0, "!!! WARNING !!! Screen and virtual resolution differ.")
|
||||
GuiTextCentered(modGUI, 0, 0, "To fix the problem, do one of these:")
|
||||
GuiTextCentered(
|
||||
modGUI,
|
||||
0,
|
||||
0,
|
||||
string.format(
|
||||
"- Change the resolution in the game options to %dx%d",
|
||||
virtualWidth * CAPTURE_PIXEL_SIZE,
|
||||
virtualHeight * CAPTURE_PIXEL_SIZE
|
||||
)
|
||||
)
|
||||
GuiTextCentered(
|
||||
modGUI,
|
||||
0,
|
||||
0,
|
||||
string.format(
|
||||
"- Change the virtual resolution in the mod to %dx%d",
|
||||
screenWidth / CAPTURE_PIXEL_SIZE,
|
||||
screenHeight / CAPTURE_PIXEL_SIZE
|
||||
)
|
||||
)
|
||||
if math.abs(ratioX - ratioY) < 0.0001 then
|
||||
GuiTextCentered(modGUI, 0, 0, string.format("- Change the CAPTURE_PIXEL_SIZE in the mod to %f", ratioX))
|
||||
end
|
||||
GuiTextCentered(modGUI, 0, 0, " ")
|
||||
problem = true
|
||||
end
|
||||
end
|
||||
|
||||
if not fileExists("mods/noita-mapcap/bin/capture-b/capture.dll") then
|
||||
GuiTextCentered(modGUI, 0, 0, "!!! WARNING !!! Can't find library for screenshots.")
|
||||
GuiTextCentered(modGUI, 0, 0, "To fix the problem, do one of these:")
|
||||
GuiTextCentered(modGUI, 0, 0, "- Redownload a release of this mod from GitHub, don't download the sourcecode")
|
||||
GuiTextCentered(modGUI, 0, 0, " ")
|
||||
problem = true
|
||||
end
|
||||
|
||||
if not fileExists("mods/noita-mapcap/bin/stitch/stitch.exe") then
|
||||
GuiTextCentered(modGUI, 0, 0, "!!! WARNING !!! Can't find software for stitching.")
|
||||
GuiTextCentered(modGUI, 0, 0, "You can still take screenshots, but you won't be able to stitch those screenshots.")
|
||||
GuiTextCentered(modGUI, 0, 0, "To fix the problem, do one of these:")
|
||||
GuiTextCentered(modGUI, 0, 0, "- Redownload a release of this mod from GitHub, don't download the sourcecode")
|
||||
GuiTextCentered(modGUI, 0, 0, " ")
|
||||
problem = true
|
||||
end
|
||||
|
||||
if not problem then
|
||||
GuiTextCentered(modGUI, 0, 0, "No problems found.")
|
||||
GuiTextCentered(modGUI, 0, 0, " ")
|
||||
end
|
||||
|
||||
GuiTextCentered(modGUI, 0, 0, "You can freely look around and search a place to start capturing.")
|
||||
GuiTextCentered(modGUI, 0, 0, "The mod will then take images in a spiral around your current view.")
|
||||
GuiTextCentered(modGUI, 0, 0, "Use ESC and close the game to stop the process.")
|
||||
GuiTextCentered(modGUI, 0, 0, "Use ESC to pause, and close the game to stop the process.")
|
||||
GuiTextCentered(
|
||||
modGUI,
|
||||
0,
|
||||
@ -29,10 +101,12 @@ async_loop(
|
||||
0,
|
||||
'If you want to start a new map, you have to delete all images from the "output" folder!'
|
||||
)
|
||||
GuiTextCentered(modGUI, 0, 0, " ")
|
||||
if GuiButton(modGUI, 0, 0, ">> Start capturing map <<", 1) then
|
||||
startCapturing()
|
||||
UiReduce = true
|
||||
end
|
||||
GuiTextCentered(modGUI, 0, 0, " ")
|
||||
end
|
||||
if not UiHide then
|
||||
local x, y = GameGetCameraPos()
|
||||
|
Loading…
Reference in New Issue
Block a user