From c6b232f5d4478af7d2c2ebfe080450931c15f414 Mon Sep 17 00:00:00 2001 From: Cameron Cawley Date: Wed, 13 May 2026 20:51:02 +0100 Subject: [PATCH] Support loading JPEG images through SDL_LoadSurface() --- include/SDL3/SDL_surface.h | 60 ++++++++++++++-- src/dynapi/SDL_dynapi.exports | 2 + src/dynapi/SDL_dynapi.sym | 2 + src/dynapi/SDL_dynapi_overrides.h | 2 + src/dynapi/SDL_dynapi_procs.h | 2 + src/video/SDL_stb.c | 110 ++++++++++++++++++++++++++++++ src/video/SDL_surface.c | 2 + src/video/SDL_surface_c.h | 1 + 8 files changed, 175 insertions(+), 6 deletions(-) diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h index 885f8f5d98..1194596cd2 100644 --- a/include/SDL3/SDL_surface.h +++ b/include/SDL3/SDL_surface.h @@ -29,10 +29,10 @@ * provides a reasonable toolbox for transforming the data, including copying * between surfaces, filling rectangles in the image data, etc. * - * There is also a simple .bmp loader, SDL_LoadBMP(), and a simple .png - * loader, SDL_LoadPNG(). SDL itself does not provide loaders for other file - * formats, but there are several excellent external libraries that do, - * including its own satellite library, + * There is also a simple .bmp loader, SDL_LoadBMP(), a simple .png loader, + * SDL_LoadPNG(), and a simple .jpg loader, SDL_LoadJPG(). SDL itself does not + * provide loaders for other file formats, but there are several excellent + * external libraries that do, including its own satellite library, * [SDL_image](https://wiki.libsdl.org/SDL3_image) * . * @@ -510,7 +510,7 @@ 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. + * Load a BMP, PNG or JPEG 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. @@ -531,7 +531,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnlockSurface(SDL_Surface *surface); extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadSurface_IO(SDL_IOStream *src, bool closeio); /** - * Load a BMP or PNG image from a file. + * Load a BMP, PNG or JPEG image from a file. * * The new surface should be freed with SDL_DestroySurface(). Not doing so * will result in a memory leak. @@ -729,6 +729,54 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SavePNG_IO(SDL_Surface *surface, SDL_IOStre */ extern SDL_DECLSPEC bool SDLCALL SDL_SavePNG(SDL_Surface *surface, const char *file); +/** + * Load a JPEG image from a seekable SDL data stream. + * + * This is intended as a convenience function for loading images from trusted + * sources. If you want to load arbitrary images you should use libjpeg or + * another image loading library designed with security in mind. + * + * 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.6.0. + * + * \sa SDL_DestroySurface + * \sa SDL_LoadJPG + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadJPG_IO(SDL_IOStream *src, bool closeio); + +/** + * Load a JPEG image from a file. + * + * This is intended as a convenience function for loading images from trusted + * sources. If you want to load arbitrary images you should use libjpeg or + * another image loading library designed with security in mind. + * + * The new surface should be freed with SDL_DestroySurface(). Not doing so + * will result in a memory leak. + * + * \param file the JPG 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.6.0. + * + * \sa SDL_DestroySurface + * \sa SDL_LoadJPG_IO + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadJPG(const char *file); + /** * Set the RLE acceleration hint for a surface. * diff --git a/src/dynapi/SDL_dynapi.exports b/src/dynapi/SDL_dynapi.exports index 5953db3622..67600f2b7b 100644 --- a/src/dynapi/SDL_dynapi.exports +++ b/src/dynapi/SDL_dynapi.exports @@ -1285,3 +1285,5 @@ _SDL_SetGPURenderStateStorageBuffers _SDL_GDKSuspendRenderer _SDL_GDKResumeRenderer _SDL_IsPhone +_SDL_LoadJPG_IO +_SDL_LoadJPG diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index a16d0f79c3..3fdc470a33 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1286,6 +1286,8 @@ SDL3_0.0.0 { SDL_GDKSuspendRenderer; SDL_GDKResumeRenderer; SDL_IsPhone; + SDL_LoadJPG_IO; + SDL_LoadJPG; # 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 c60b5223d4..7b88affdc6 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1312,3 +1312,5 @@ #define SDL_GDKSuspendRenderer SDL_GDKSuspendRenderer_REAL #define SDL_GDKResumeRenderer SDL_GDKResumeRenderer_REAL #define SDL_IsPhone SDL_IsPhone_REAL +#define SDL_LoadJPG_IO SDL_LoadJPG_IO_REAL +#define SDL_LoadJPG SDL_LoadJPG_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 0d2d4ce5c7..24a5afad98 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1320,3 +1320,5 @@ SDL_DYNAPI_PROC(bool,SDL_SetGPURenderStateStorageBuffers,(SDL_GPURenderState *a, SDL_DYNAPI_PROC(void,SDL_GDKSuspendRenderer,(SDL_Renderer *a),(a),) SDL_DYNAPI_PROC(void,SDL_GDKResumeRenderer,(SDL_Renderer *a),(a),) SDL_DYNAPI_PROC(bool,SDL_IsPhone,(void),(),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadJPG_IO,(SDL_IOStream *a,bool b),(a,b),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadJPG,(const char *a),(a),return) diff --git a/src/video/SDL_stb.c b/src/video/SDL_stb.c index 732b4ef016..fe5d0c70e9 100644 --- a/src/video/SDL_stb.c +++ b/src/video/SDL_stb.c @@ -363,6 +363,116 @@ static SDL_Surface *SDL_LoadSTB_IO(SDL_IOStream *src) } #endif // SDL_HAVE_STB +/* FIXME: This is a copypaste from JPEGLIB! Pull that out of the ifdefs */ +/* Define this for quicker (but less perfect) JPEG identification */ +#define FAST_IS_JPEG + +/* See if an image is contained in a data source */ +bool SDL_IsJPG(SDL_IOStream *src) +{ + Sint64 start; + bool is_JPG; + bool in_scan; + Uint8 magic[4]; + + /* This detection code is by Steaphan Greene */ + /* Blame me, not Sam, if this doesn't work right. */ + /* And don't forget to report the problem to the the sdl list too! */ + + if (!src) { + return false; + } + + start = SDL_TellIO(src); + is_JPG = false; + in_scan = false; + if (SDL_ReadIO(src, magic, 2) == 2) { + if ( (magic[0] == 0xFF) && (magic[1] == 0xD8) ) { + is_JPG = true; + while (is_JPG) { + if (SDL_ReadIO(src, magic, 2) != 2) { + is_JPG = false; + } else if ( (magic[0] != 0xFF) && !in_scan ) { + is_JPG = false; + } else if ( (magic[0] != 0xFF) || (magic[1] == 0xFF) ) { + /* Extra padding in JPEG (legal) */ + /* or this is data and we are scanning */ + SDL_SeekIO(src, -1, SDL_IO_SEEK_CUR); + } else if (magic[1] == 0xD9) { + /* Got to end of good JPEG */ + break; + } else if ( in_scan && (magic[1] == 0x00) ) { + /* This is an encoded 0xFF within the data */ + } else if ( (magic[1] >= 0xD0) && (magic[1] < 0xD9) ) { + /* These have nothing else */ + } else if (SDL_ReadIO(src, magic+2, 2) != 2) { + is_JPG = false; + } else { + /* Yes, it's big-endian */ + Sint64 innerStart; + Uint32 size; + Sint64 end; + innerStart = SDL_TellIO(src); + size = (magic[2] << 8) + magic[3]; + end = SDL_SeekIO(src, size-2, SDL_IO_SEEK_CUR); + if ( end != innerStart + size - 2 ) { + is_JPG = false; + } + if ( magic[1] == 0xDA ) { + /* Now comes the actual JPEG meat */ +#ifdef FAST_IS_JPEG + /* Ok, I'm convinced. It is a JPEG. */ + break; +#else + /* I'm not convinced. Prove it! */ + in_scan = true; +#endif + } + } + } + } + } + SDL_SeekIO(src, start, SDL_IO_SEEK_SET); + return is_JPG; +} + +SDL_Surface *SDL_LoadJPG_IO(SDL_IOStream *src, bool closeio) +{ + SDL_Surface *surface = NULL; + + CHECK_PARAM(!src) { + SDL_InvalidParamError("src"); + goto done; + } + + if (!SDL_IsJPG(src)) { + SDL_SetError("File is not a JPEG file"); + goto done; + } + +#ifdef SDL_HAVE_STB + surface = SDL_LoadSTB_IO(src); +#else + SDL_SetError("SDL not built with STB image support"); +#endif // SDL_HAVE_STB + +done: + if (src && closeio) { + SDL_CloseIO(src); + } + return surface; +} + +SDL_Surface *SDL_LoadJPG(const char *file) +{ + SDL_IOStream *stream = SDL_IOFromFile(file, "rb"); + if (!stream) { + return NULL; + } + + return SDL_LoadJPG_IO(stream, true); +} + bool SDL_IsPNG(SDL_IOStream *src) { Sint64 start; diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c index 8164f76fbc..6a03fad732 100644 --- a/src/video/SDL_surface.c +++ b/src/video/SDL_surface.c @@ -3118,6 +3118,8 @@ SDL_Surface *SDL_LoadSurface_IO(SDL_IOStream *src, bool closeio) return SDL_LoadBMP_IO(src, closeio); } else if (SDL_IsPNG(src)) { return SDL_LoadPNG_IO(src, closeio); + } else if (SDL_IsJPG(src)) { + return SDL_LoadJPG_IO(src, closeio); } else { if (closeio) { SDL_CloseIO(src); diff --git a/src/video/SDL_surface_c.h b/src/video/SDL_surface_c.h index 168f05e515..231cc7430b 100644 --- a/src/video/SDL_surface_c.h +++ b/src/video/SDL_surface_c.h @@ -94,6 +94,7 @@ extern float SDL_GetSurfaceHDRHeadroom(SDL_Surface *surface, SDL_Colorspace colo extern SDL_Surface *SDL_GetSurfaceImage(SDL_Surface *surface, float display_scale); extern SDL_Surface *SDL_ConvertSurfaceRect(SDL_Surface *surface, const SDL_Rect *rect, SDL_PixelFormat format); extern bool SDL_IsBMP(SDL_IOStream *src); +extern bool SDL_IsJPG(SDL_IOStream *src); extern bool SDL_IsPNG(SDL_IOStream *src); #endif // SDL_surface_c_h_