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.
This commit is contained in:
Misa 2024-01-09 11:29:50 -08:00 committed by Misa Elizabeth Kai
parent 060fe6938d
commit f05827f268
9 changed files with 170 additions and 1 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -332,6 +332,7 @@ public:
SDL_Texture* backgroundTexture;
SDL_Texture* foregroundTexture;
SDL_Texture* tempScrollingTexture;
SDL_Surface* tempScreenshot;
TowerBG towerbg;
TowerBG titlebg;

View File

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

View File

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

View File

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

View File

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