From e441283237142c6455757aaf3b81b5bcf85e3ffe Mon Sep 17 00:00:00 2001 From: Maia Date: Sat, 1 Nov 2025 19:58:18 +0100 Subject: [PATCH] Add SDL_LoadSurface and SDL_LoadSurface_IO --- include/SDL3/SDL_surface.h | 42 +++++++++++++++++++++++++++++++ src/dynapi/SDL_dynapi.sym | 2 ++ src/dynapi/SDL_dynapi_overrides.h | 2 ++ src/dynapi/SDL_dynapi_procs.h | 2 ++ src/video/SDL_bmp.c | 30 +++++++++++++++++----- src/video/SDL_stb.c | 36 +++++++++++++++----------- src/video/SDL_surface.c | 25 ++++++++++++++++++ src/video/SDL_surface_c.h | 2 ++ test/testcustomcursor.c | 2 +- test/testshape.c | 2 +- test/testutils.c | 2 +- 11 files changed, 124 insertions(+), 23 deletions(-) diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h index 880115288316b..5099db921ef38 100644 --- a/include/SDL3/SDL_surface.h +++ b/include/SDL3/SDL_surface.h @@ -502,6 +502,48 @@ extern SDL_DECLSPEC bool SDLCALL SDL_LockSurface(SDL_Surface *surface); */ extern SDL_DECLSPEC void SDLCALL SDL_UnlockSurface(SDL_Surface *surface); +/** + * Load a BMP or PNG image from a seekable SDL data stream. + * + * The new surface should be freed with SDL_DestroySurface(). Not doing so + * will result in a memory leak. + * + * \param src the data stream for the surface. + * \param closeio if true, calls SDL_CloseIO() on `src` before returning, even + * in the case of an error. + * \returns a pointer to a new SDL_Surface structure or NULL on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_DestroySurface + * \sa SDL_LoadBMP + * \sa SDL_SaveBMP_IO + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadSurface_IO(SDL_IOStream *src, bool closeio); + +/** + * Load a BMP or PNG image from a file. + * + * The new surface should be freed with SDL_DestroySurface(). Not doing so + * will result in a memory leak. + * + * \param file the BMP file to load. + * \returns a pointer to a new SDL_Surface structure or NULL on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.4.0. + * + * \sa SDL_DestroySurface + * \sa SDL_LoadBMP_IO + * \sa SDL_SaveBMP + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadSurface(const char *file); + /** * Load a BMP image from a seekable SDL data stream. * diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 9feb8a0fc6f7d..de64a3efad92a 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1268,6 +1268,8 @@ SDL3_0.0.0 { SDL_GetPenDeviceType; SDL_CreateAnimatedCursor; SDL_RotateSurface; + SDL_LoadSurface_IO; + SDL_LoadSurface; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 1d17c7fcf0a2f..cc53044efb606 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1294,3 +1294,5 @@ #define SDL_GetPenDeviceType SDL_GetPenDeviceType_REAL #define SDL_CreateAnimatedCursor SDL_CreateAnimatedCursor_REAL #define SDL_RotateSurface SDL_RotateSurface_REAL +#define SDL_LoadSurface_IO SDL_LoadSurface_IO_REAL +#define SDL_LoadSurface SDL_LoadSurface_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index a2fc06fd7f14d..9e3c9cdbef2bd 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1302,3 +1302,5 @@ SDL_DYNAPI_PROC(int,SDL_GetSystemPageSize,(void),(),return) SDL_DYNAPI_PROC(SDL_PenDeviceType,SDL_GetPenDeviceType,(SDL_PenID a),(a),return) SDL_DYNAPI_PROC(SDL_Cursor*,SDL_CreateAnimatedCursor,(SDL_CursorFrameInfo *a,int b,int c,int d),(a,b,c,d),return) SDL_DYNAPI_PROC(SDL_Surface*,SDL_RotateSurface,(SDL_Surface *a,float b),(a,b),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadSurface_IO,(SDL_IOStream *a,bool b),(a,b),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadSurface,(const char *a),(a),return) diff --git a/src/video/SDL_bmp.c b/src/video/SDL_bmp.c index e9c21333da317..766710694df17 100644 --- a/src/video/SDL_bmp.c +++ b/src/video/SDL_bmp.c @@ -177,6 +177,26 @@ static void CorrectAlphaChannel(SDL_Surface *surface) } } +bool SDL_IsBMP(SDL_IOStream* src) +{ + Sint64 start; + Uint8 magic[2]; + bool is_BMP; + + is_BMP = false; + start = SDL_TellIO(src); + if (start >= 0) { + if (SDL_ReadIO(src, magic, sizeof(magic)) == sizeof(magic)) { + if (magic[0] == 'B' && magic[1] == 'M') { + is_BMP = true; + } + } + SDL_SeekIO(src, start, SDL_IO_SEEK_SET); + } + + return is_BMP; +} + SDL_Surface *SDL_LoadBMP_IO(SDL_IOStream *src, bool closeio) { bool was_error = true; @@ -195,7 +215,7 @@ SDL_Surface *SDL_LoadBMP_IO(SDL_IOStream *src, bool closeio) bool correctAlpha = false; // The Win32 BMP file header (14 bytes) - char magic[2]; + // char magic[2]; // Uint32 bfSize; // Uint16 bfReserved1; // Uint16 bfReserved2; @@ -227,14 +247,12 @@ SDL_Surface *SDL_LoadBMP_IO(SDL_IOStream *src, bool closeio) goto done; } SDL_ClearError(); - if (SDL_ReadIO(src, magic, 2) != 2) { - goto done; - } - if (SDL_strncmp(magic, "BM", 2) != 0) { + if (!SDL_IsBMP(src)) { SDL_SetError("File is not a Windows BMP file"); goto done; } - if (!SDL_ReadU32LE(src, NULL /* bfSize */) || + if (!SDL_ReadU16LE(src, NULL /* magic (already checked) */) || + !SDL_ReadU32LE(src, NULL /* bfSize */) || !SDL_ReadU16LE(src, NULL /* bfReserved1 */) || !SDL_ReadU16LE(src, NULL /* bfReserved2 */) || !SDL_ReadU32LE(src, &bfOffBits)) { diff --git a/src/video/SDL_stb.c b/src/video/SDL_stb.c index 1073ba658a7ab..5f33ebf1ceb2b 100644 --- a/src/video/SDL_stb.c +++ b/src/video/SDL_stb.c @@ -328,11 +328,31 @@ static SDL_Surface *SDL_LoadSTB_IO(SDL_IOStream *src) } #endif // SDL_HAVE_STB -SDL_Surface *SDL_LoadPNG_IO(SDL_IOStream *src, bool closeio) +bool SDL_IsPNG(SDL_IOStream* src) { Sint64 start; Uint8 magic[4]; bool is_PNG; + + is_PNG = false; + start = SDL_TellIO(src); + if (start >= 0) { + if (SDL_ReadIO(src, magic, sizeof(magic)) == sizeof(magic)) { + if (magic[0] == 0x89 && + magic[1] == 'P' && + magic[2] == 'N' && + magic[3] == 'G') { + is_PNG = true; + } + } + SDL_SeekIO(src, start, SDL_IO_SEEK_SET); + } + + return is_PNG; +} + +SDL_Surface *SDL_LoadPNG_IO(SDL_IOStream *src, bool closeio) +{ SDL_Surface *surface = NULL; CHECK_PARAM(!src) { @@ -340,19 +360,7 @@ SDL_Surface *SDL_LoadPNG_IO(SDL_IOStream *src, bool closeio) goto done; } - start = SDL_TellIO(src); - is_PNG = false; - if (SDL_ReadIO(src, magic, sizeof(magic)) == sizeof(magic)) { - if (magic[0] == 0x89 && - magic[1] == 'P' && - magic[2] == 'N' && - magic[3] == 'G') { - is_PNG = true; - } - } - SDL_SeekIO(src, start, SDL_IO_SEEK_SET); - - if (!is_PNG) { + if (!SDL_IsPNG(src)) { SDL_SetError("File is not a PNG file"); goto done; } diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c index 104b7b9d5dc7a..9253abc7fcee8 100644 --- a/src/video/SDL_surface.c +++ b/src/video/SDL_surface.c @@ -3086,3 +3086,28 @@ void SDL_DestroySurface(SDL_Surface *surface) SDL_free(surface); } } + +SDL_Surface *SDL_LoadSurface_IO(SDL_IOStream *src, bool closeio) +{ + if (SDL_IsBMP(src)) { + return SDL_LoadBMP_IO(src, closeio); + } else if (SDL_IsPNG(src)) { + return SDL_LoadPNG_IO(src, closeio); + } else { + if (closeio) { + SDL_CloseIO(src); + } + SDL_SetError("Unsupported image format"); + return NULL; + } +} + +SDL_Surface *SDL_LoadSurface(const char *file) +{ + SDL_IOStream *stream = SDL_IOFromFile(file, "rb"); + if (!stream) { + return NULL; + } + + return SDL_LoadSurface_IO(stream, true); +} diff --git a/src/video/SDL_surface_c.h b/src/video/SDL_surface_c.h index 9e73a4bca7adc..eb6bb038206a3 100644 --- a/src/video/SDL_surface_c.h +++ b/src/video/SDL_surface_c.h @@ -89,5 +89,7 @@ extern float SDL_GetSurfaceSDRWhitePoint(SDL_Surface *surface, SDL_Colorspace co extern float SDL_GetDefaultHDRHeadroom(SDL_Colorspace colorspace); extern float SDL_GetSurfaceHDRHeadroom(SDL_Surface *surface, SDL_Colorspace colorspace); extern SDL_Surface *SDL_GetSurfaceImage(SDL_Surface *surface, float display_scale); +extern bool SDL_IsBMP(SDL_IOStream* src); +extern bool SDL_IsPNG(SDL_IOStream* src); #endif // SDL_surface_c_h_ diff --git a/test/testcustomcursor.c b/test/testcustomcursor.c index b3ac20bfdfc63..b5c6a09f06bea 100644 --- a/test/testcustomcursor.c +++ b/test/testcustomcursor.c @@ -111,7 +111,7 @@ static const char *cross[] = { static SDL_Surface *load_image_file(const char *file) { - SDL_Surface *surface = SDL_strstr(file, ".png") ? SDL_LoadPNG(file) : SDL_LoadBMP(file); + SDL_Surface *surface = SDL_LoadSurface(file); if (surface) { if (SDL_GetSurfacePalette(surface)) { const Uint8 bpp = SDL_BITSPERPIXEL(surface->format); diff --git a/test/testshape.c b/test/testshape.c index 3198d3870d83f..4386e6a7c004b 100644 --- a/test/testshape.c +++ b/test/testshape.c @@ -54,7 +54,7 @@ int main(int argc, char *argv[]) } if (image_file) { - shape = SDL_strstr(image_file, ".png") ? SDL_LoadPNG(image_file) : SDL_LoadBMP(image_file); + shape = SDL_LoadSurface(image_file); if (!shape) { SDL_Log("Couldn't load %s: %s", image_file, SDL_GetError()); goto quit; diff --git a/test/testutils.c b/test/testutils.c index 4acf985fac447..bbe809081070f 100644 --- a/test/testutils.c +++ b/test/testutils.c @@ -84,7 +84,7 @@ SDL_Texture *LoadTexture(SDL_Renderer *renderer, const char *file, bool transpar file = path; } - temp = SDL_strstr(file, ".png") ? SDL_LoadPNG(file) : SDL_LoadBMP(file); + temp = SDL_LoadSurface(file); if (!temp) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s", file, SDL_GetError()); } else {