Support loading JPEG images through SDL_LoadSurface()

This commit is contained in:
Cameron Cawley
2026-05-13 20:51:02 +01:00
committed by Sam Lantinga
parent 9672f5b68b
commit c6b232f5d4
8 changed files with 175 additions and 6 deletions

View File

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

View File

@@ -1285,3 +1285,5 @@ _SDL_SetGPURenderStateStorageBuffers
_SDL_GDKSuspendRenderer
_SDL_GDKResumeRenderer
_SDL_IsPhone
_SDL_LoadJPG_IO
_SDL_LoadJPG

View File

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

View File

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

View File

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

View File

@@ -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 <stea@cs.binghamton.edu> */
/* 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;

View File

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

View File

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