From f05827f268e28268f96baa08e41d6debde4b4c88 Mon Sep 17 00:00:00 2001 From: Misa Date: Tue, 9 Jan 2024 11:29:50 -0800 Subject: [PATCH] Add support for internal screenshots "But people already have screenshot tools", you might protest. The rationale is simple: If you play with any video setting other than 1x windowed (no stretching and no letterbox), then your screenshot will be too big if you want the internal resolution of 320x240, and downscaling will be an inconvenience. The point is to make screenshots based off of internal resolution so they are always pixel perfect and ideally never have to be altered once taken. I've added the keybind of F6 to do this. Right now it saves to a temporary test location with the same filename; future commits will save to properly-timestamped filenames. --- desktop_version/CMakeLists.txt | 1 - desktop_version/src/FileSystemUtils.cpp | 42 +++++++++++++++++ desktop_version/src/FileSystemUtils.h | 1 + desktop_version/src/Graphics.cpp | 2 + desktop_version/src/Graphics.h | 1 + desktop_version/src/GraphicsResources.cpp | 57 +++++++++++++++++++++++ desktop_version/src/GraphicsUtil.cpp | 57 +++++++++++++++++++++++ desktop_version/src/GraphicsUtil.h | 2 + desktop_version/src/KeyPoll.cpp | 8 ++++ 9 files changed, 170 insertions(+), 1 deletion(-) diff --git a/desktop_version/CMakeLists.txt b/desktop_version/CMakeLists.txt index 0d4b8a7e..9e60db8b 100644 --- a/desktop_version/CMakeLists.txt +++ b/desktop_version/CMakeLists.txt @@ -316,7 +316,6 @@ add_library(lodepng-static STATIC ${PNG_SRC}) target_compile_definitions(lodepng-static PRIVATE -DLODEPNG_NO_COMPILE_ALLOCATORS -DLODEPNG_NO_COMPILE_DISK - -DLODEPNG_NO_COMPILE_ENCODER ) add_library(c-hashmap-static STATIC ${CHM_SRC}) diff --git a/desktop_version/src/FileSystemUtils.cpp b/desktop_version/src/FileSystemUtils.cpp index d9813d56..f23f86d3 100644 --- a/desktop_version/src/FileSystemUtils.cpp +++ b/desktop_version/src/FileSystemUtils.cpp @@ -48,6 +48,7 @@ static char* basePath = NULL; static char writeDir[MAX_PATH] = {'\0'}; static char saveDir[MAX_PATH] = {'\0'}; static char levelDir[MAX_PATH] = {'\0'}; +static char screenshotDir[MAX_PATH] = {'\0'}; static char mainLangDir[MAX_PATH] = {'\0'}; static bool isMainLangDirFromRepo = false; static bool doesLangDirExist = false; @@ -260,6 +261,15 @@ int FILESYSTEM_init(char *argvZero, char* baseDir, char *assetsPath, char* langD mkdir(levelDir, 0777); vlog_info("Level directory: %s", levelDir); + /* Store full screenshot directory */ + SDL_snprintf(screenshotDir, sizeof(screenshotDir), "%s%s%s", + writeDir, + "screenshots", + pathSep + ); + mkdir(screenshotDir, 0777); + vlog_info("Screenshot directory: %s", screenshotDir); + basePath = SDL_GetBasePath(); if (basePath == NULL) @@ -799,6 +809,38 @@ static PHYSFS_sint64 read_bytes( return bytes_read; } +bool FILESYSTEM_saveFile(const char* name, const unsigned char* data, const size_t len) +{ + if (!isInit) + { + vlog_warn("Filesystem not initialized! Not writing just to be safe."); + return false; + } + + PHYSFS_File* handle = PHYSFS_openWrite(name); + if (handle == NULL) + { + vlog_error( + "Could not open PHYSFS handle for %s: %s", + name, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()) + ); + return false; + } + PHYSFS_sint64 bytes_written = PHYSFS_writeBytes(handle, data, len); + if ((size_t) bytes_written != len) + { + vlog_warn("%s: Number of bytes written is not as expected", name); + } + + int success = PHYSFS_close(handle); + if (success == 0) + { + vlog_error("%s: Could not close handle", name); + } + + return true; +} + void FILESYSTEM_loadFileToMemory( const char *name, unsigned char **mem, diff --git a/desktop_version/src/FileSystemUtils.h b/desktop_version/src/FileSystemUtils.h index 667f180c..e5c65031 100644 --- a/desktop_version/src/FileSystemUtils.h +++ b/desktop_version/src/FileSystemUtils.h @@ -32,6 +32,7 @@ void FILESYSTEM_unmountAssets(void); bool FILESYSTEM_isAssetMounted(const char* filename); bool FILESYSTEM_areAssetsInSameRealDir(const char* filenameA, const char* filenameB); +bool FILESYSTEM_saveFile(const char* name, const unsigned char* data, size_t len); void FILESYSTEM_loadFileToMemory(const char *name, unsigned char **mem, size_t *len); void FILESYSTEM_loadAssetToMemory( diff --git a/desktop_version/src/Graphics.cpp b/desktop_version/src/Graphics.cpp index fefae038..02611a0f 100644 --- a/desktop_version/src/Graphics.cpp +++ b/desktop_version/src/Graphics.cpp @@ -111,6 +111,7 @@ void Graphics::init(void) tempShakeTexture = NULL; backgroundTexture = NULL; foregroundTexture = NULL; + tempScreenshot = NULL; towerbg = TowerBG(); titlebg = TowerBG(); trinketr = 0; @@ -220,6 +221,7 @@ void Graphics::destroy_buffers(void) VVV_freefunc(SDL_DestroyTexture, titlebg.texture); VVV_freefunc(SDL_FreeSurface, tempFilterSrc); VVV_freefunc(SDL_FreeSurface, tempFilterDest); + VVV_freefunc(SDL_FreeSurface, tempScreenshot); } void Graphics::drawspritesetcol(int x, int y, int t, int c) diff --git a/desktop_version/src/Graphics.h b/desktop_version/src/Graphics.h index ec201c62..10167520 100644 --- a/desktop_version/src/Graphics.h +++ b/desktop_version/src/Graphics.h @@ -332,6 +332,7 @@ public: SDL_Texture* backgroundTexture; SDL_Texture* foregroundTexture; SDL_Texture* tempScrollingTexture; + SDL_Surface* tempScreenshot; TowerBG towerbg; TowerBG titlebg; diff --git a/desktop_version/src/GraphicsResources.cpp b/desktop_version/src/GraphicsResources.cpp index 29f5358a..a74b6712 100644 --- a/desktop_version/src/GraphicsResources.cpp +++ b/desktop_version/src/GraphicsResources.cpp @@ -4,6 +4,7 @@ #include "Alloc.h" #include "FileSystemUtils.h" +#include "Graphics.h" #include "GraphicsUtil.h" #include "Localization.h" #include "Vlogging.h" @@ -20,6 +21,13 @@ extern "C" const unsigned char* in, size_t insize ); + extern unsigned lodepng_encode24( + unsigned char** out, + size_t* outsize, + const unsigned char* image, + unsigned w, + unsigned h + ); extern const char* lodepng_error_text(unsigned code); } @@ -469,3 +477,52 @@ void GraphicsResources::destroy(void) VVV_freefunc(SDL_FreeSurface, im_sprites_surf); VVV_freefunc(SDL_FreeSurface, im_flipsprites_surf); } + +bool SaveImage(const SDL_Surface* surface, const char* filename) +{ + unsigned char* out; + size_t outsize; + unsigned int error; + bool success; + + error = lodepng_encode24( + &out, &outsize, + (const unsigned char*) surface->pixels, + surface->w, surface->h + ); + + if (error != 0) + { + vlog_error("Could not save image: %s", lodepng_error_text(error)); + return false; + } + + success = FILESYSTEM_saveFile(filename, out, outsize); + SDL_free(out); + + if (!success) + { + vlog_error("Could not save image"); + } + + return success; +} + +bool SaveScreenshot(void) +{ + bool success = TakeScreenshot(&graphics.tempScreenshot); + if (!success) + { + vlog_error("Could not take screenshot"); + return false; + } + // TODO: Timestamp in filename + success = SaveImage(graphics.tempScreenshot, "screenshots/test.png"); + if (!success) + { + return false; + } + + vlog_info("Saved screenshot"); + return true; +} diff --git a/desktop_version/src/GraphicsUtil.cpp b/desktop_version/src/GraphicsUtil.cpp index 303e12f5..255e3f5e 100644 --- a/desktop_version/src/GraphicsUtil.cpp +++ b/desktop_version/src/GraphicsUtil.cpp @@ -257,3 +257,60 @@ void ApplyFilter(SDL_Surface** src, SDL_Surface** dest) SDL_UpdateTexture(graphics.gameTexture, NULL, (*dest)->pixels, (*dest)->pitch); } + +bool TakeScreenshot(SDL_Surface** surface) +{ + if (surface == NULL) + { + SDL_assert(0 && "surface is NULL!"); + return false; + } + + int width = 0; + int height = 0; + int result = graphics.query_texture( + graphics.gameTexture, NULL, NULL, &width, &height + ); + if (result != 0) + { + return false; + } + + if (*surface == NULL) + { + *surface = SDL_CreateRGBSurface(0, width, height, 24, 0, 0, 0, 0); + if (*surface == NULL) + { + WHINE_ONCE_ARGS( + ("Could not create temporary surface: %s", SDL_GetError()) + ); + return false; + } + } + + if ((*surface)->w != width || (*surface)->h != height) + { + SDL_assert(0 && "Width and height of surface and texture mismatch!"); + return false; + } + + result = graphics.set_render_target(graphics.gameTexture); + if (result != 0) + { + return false; + } + + result = SDL_RenderReadPixels( + gameScreen.m_renderer, NULL, SDL_PIXELFORMAT_RGB24, + (*surface)->pixels, (*surface)->pitch + ); + if (result != 0) + { + WHINE_ONCE_ARGS( + ("Could not read pixels from renderer: %s", SDL_GetError()) + ); + return false; + } + + return true; +} diff --git a/desktop_version/src/GraphicsUtil.h b/desktop_version/src/GraphicsUtil.h index cb59df98..ffa89b51 100644 --- a/desktop_version/src/GraphicsUtil.h +++ b/desktop_version/src/GraphicsUtil.h @@ -14,4 +14,6 @@ SDL_Color ReadPixel(const SDL_Surface* surface, int x, int y); void UpdateFilter(void); void ApplyFilter(SDL_Surface** src, SDL_Surface** dest); +bool TakeScreenshot(SDL_Surface** surface); + #endif /* GRAPHICSUTIL_H */ diff --git a/desktop_version/src/KeyPoll.cpp b/desktop_version/src/KeyPoll.cpp index 43a1831f..444bad0e 100644 --- a/desktop_version/src/KeyPoll.cpp +++ b/desktop_version/src/KeyPoll.cpp @@ -10,6 +10,7 @@ #include "Game.h" #include "GlitchrunnerMode.h" #include "Graphics.h" +#include "GraphicsUtil.h" #include "Localization.h" #include "LocalizationStorage.h" #include "Music.h" @@ -17,6 +18,8 @@ #include "UTF8.h" #include "Vlogging.h" +bool SaveScreenshot(void); + int inline KeyPoll::getThreshold(void) { switch (sensitivity) @@ -181,6 +184,11 @@ void KeyPoll::Poll(void) music.playef(Sound_COIN); } + if (evt.key.keysym.sym == SDLK_F6 && !evt.key.repeat) + { + SaveScreenshot(); + } + BUTTONGLYPHS_keyboard_set_active(true); if (textentry())