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:
David Vogel 2019-11-01 02:40:21 +01:00
parent 638f6223c3
commit c87a4d05d0
9 changed files with 235 additions and 92 deletions

View File

@ -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

View File

@ -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.

View File

@ -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")

View File

@ -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.")

View File

@ -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()

View File

@ -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

View File

@ -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>

View File

@ -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()