diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h index ad57a4d910..ba2a5e172d 100644 --- a/include/SDL3/SDL_surface.h +++ b/include/SDL3/SDL_surface.h @@ -559,7 +559,7 @@ extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadBMP(const char *file); extern SDL_DECLSPEC bool SDLCALL SDL_SaveBMP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio); /** - * Save a surface to a file. + * Save a surface to a file in BMP format. * * Surfaces with a 24-bit, 32-bit and paletted 8-bit format get saved in the * BMP directly. Other RGB formats with 8-bit or higher get converted to a @@ -581,6 +581,84 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SaveBMP_IO(SDL_Surface *surface, SDL_IOStre */ extern SDL_DECLSPEC bool SDLCALL SDL_SaveBMP(SDL_Surface *surface, const char *file); +/** + * Load a 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.2.0. + * + * \sa SDL_DestroySurface + * \sa SDL_LoadPNG + * \sa SDL_SavePNG_IO + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadPNG_IO(SDL_IOStream *src, bool closeio); + +/** + * Load a 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 PNG 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.2.0. + * + * \sa SDL_DestroySurface + * \sa SDL_LoadPNG_IO + * \sa SDL_SavePNG + */ +extern SDL_DECLSPEC SDL_Surface * SDLCALL SDL_LoadPNG(const char *file); + +/** + * Save a surface to a seekable SDL data stream in PNG format. + * + * \param surface the SDL_Surface structure containing the image to be saved. + * \param dst a data stream to save to. + * \param closeio if true, calls SDL_CloseIO() on `dst` before returning, even + * in the case of an error. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function is not thread safe. + * + * \since This function is available since SDL 3.2.0. + * + * \sa SDL_LoadPNG_IO + * \sa SDL_SavePNG + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio); + +/** + * Save a surface to a file in PNG format. + * + * \param surface the SDL_Surface structure containing the image to be saved. + * \param file a file to save to. + * \returns true on success or false on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function is not thread safe. + * + * \since This function is available since SDL 3.2.0. + * + * \sa SDL_LoadPNG + * \sa SDL_SavePNG_IO + */ +extern SDL_DECLSPEC bool SDLCALL SDL_SavePNG(SDL_Surface *surface, const char *file); + /** * Set the RLE acceleration hint for a surface. * diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 967a424970..3aa7a822e6 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1260,6 +1260,10 @@ SDL3_0.0.0 { SDL_SetTexturePalette; SDL_GetTexturePalette; SDL_GetGPURendererDevice; + SDL_LoadPNG_IO; + SDL_LoadPNG; + SDL_SavePNG_IO; + SDL_SavePNG; # 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 b0693c5471..f0693ad980 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1286,3 +1286,7 @@ #define SDL_SetTexturePalette SDL_SetTexturePalette_REAL #define SDL_GetTexturePalette SDL_GetTexturePalette_REAL #define SDL_GetGPURendererDevice SDL_GetGPURendererDevice_REAL +#define SDL_LoadPNG_IO SDL_LoadPNG_IO_REAL +#define SDL_LoadPNG SDL_LoadPNG_REAL +#define SDL_SavePNG_IO SDL_SavePNG_IO_REAL +#define SDL_SavePNG SDL_SavePNG_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 18195db8a8..4e3ca27e3e 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1294,3 +1294,7 @@ SDL_DYNAPI_PROC(Sint32,JNI_OnLoad,(JavaVM *a, void *b),(a,b),return) SDL_DYNAPI_PROC(bool,SDL_SetTexturePalette,(SDL_Texture *a,SDL_Palette *b),(a,b),return) SDL_DYNAPI_PROC(SDL_Palette*,SDL_GetTexturePalette,(SDL_Texture *a),(a),return) SDL_DYNAPI_PROC(SDL_GPUDevice*,SDL_GetGPURendererDevice,(SDL_Renderer *a),(a),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadPNG_IO,(SDL_IOStream *a,bool b),(a,b),return) +SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadPNG,(const char *a),(a),return) +SDL_DYNAPI_PROC(bool,SDL_SavePNG_IO,(SDL_Surface *a,SDL_IOStream *b,bool c),(a,b,c),return) +SDL_DYNAPI_PROC(bool,SDL_SavePNG,(SDL_Surface *a,const char *b),(a,b),return) diff --git a/src/video/SDL_stb.c b/src/video/SDL_stb.c index a7f819038f..9f4285f15e 100644 --- a/src/video/SDL_stb.c +++ b/src/video/SDL_stb.c @@ -48,12 +48,11 @@ #if defined(SDL_NEON_INTRINSICS) #define STBI_NEON #endif +#define STBI_ONLY_PNG #define STBI_ONLY_JPEG #define STBI_NO_GIF -#define STBI_NO_PNG #define STBI_NO_HDR #define STBI_NO_LINEAR -#define STBI_NO_ZLIB #define STBI_NO_STDIO #define STBI_ASSERT SDL_assert #define STB_IMAGE_IMPLEMENTATION @@ -127,6 +126,252 @@ bool SDL_ConvertPixels_STB(int width, int height, #endif } +#ifdef SDL_HAVE_STB +static int IMG_LoadSTB_IO_read(void *user, char *data, int size) +{ + size_t amount = SDL_ReadIO((SDL_IOStream*)user, data, size); + return (int)amount; +} + +static void IMG_LoadSTB_IO_skip(void *user, int n) +{ + SDL_SeekIO((SDL_IOStream*)user, n, SDL_IO_SEEK_CUR); +} + +static int IMG_LoadSTB_IO_eof(void *user) +{ + SDL_IOStream *src = (SDL_IOStream*)user; + return SDL_GetIOStatus(src) == SDL_IO_STATUS_EOF; +} + +static SDL_Surface *SDL_LoadSTB_IO(SDL_IOStream *src) +{ + Sint64 start; + Uint8 magic[26]; + int w, h, format; + stbi_uc *pixels; + stbi_io_callbacks rw_callbacks; + SDL_Surface *surface = NULL; + bool use_palette = false; + unsigned int palette_colors[256]; + + // src has already been validated + start = SDL_TellIO(src); + + if (SDL_ReadIO(src, magic, sizeof(magic)) == sizeof(magic)) { + const Uint8 PNG_COLOR_INDEXED = 3; + if (magic[0] == 0x89 && + magic[1] == 'P' && + magic[2] == 'N' && + magic[3] == 'G' && + magic[12] == 'I' && + magic[13] == 'H' && + magic[14] == 'D' && + magic[15] == 'R' && + magic[25] == PNG_COLOR_INDEXED) { + use_palette = true; + } + } + SDL_SeekIO(src, start, SDL_IO_SEEK_SET); + + /* Load the image data */ + rw_callbacks.read = IMG_LoadSTB_IO_read; + rw_callbacks.skip = IMG_LoadSTB_IO_skip; + rw_callbacks.eof = IMG_LoadSTB_IO_eof; + w = h = format = 0; /* silence warning */ + if (use_palette) { + /* Unused palette entries will be opaque white */ + SDL_memset(palette_colors, 0xff, sizeof(palette_colors)); + + pixels = stbi_load_from_callbacks_with_palette( + &rw_callbacks, + src, + &w, + &h, + palette_colors, + SDL_arraysize(palette_colors) + ); + } else { + pixels = stbi_load_from_callbacks( + &rw_callbacks, + src, + &w, + &h, + &format, + STBI_default + ); + } + if (!pixels) { + SDL_SeekIO(src, start, SDL_IO_SEEK_SET); + return NULL; + } + + if (use_palette) { + surface = SDL_CreateSurfaceFrom( + w, + h, + SDL_PIXELFORMAT_INDEX8, + pixels, + w + ); + if (surface) { + bool has_colorkey = false; + int colorkey_index = -1; + bool has_alpha = false; + SDL_Palette *palette = SDL_CreateSurfacePalette(surface); + if (palette) { + int i; + Uint8 *palette_bytes = (Uint8 *)palette_colors; + + for (i = 0; i < palette->ncolors; i++) { + palette->colors[i].r = *palette_bytes++; + palette->colors[i].g = *palette_bytes++; + palette->colors[i].b = *palette_bytes++; + palette->colors[i].a = *palette_bytes++; + if (palette->colors[i].a != SDL_ALPHA_OPAQUE) { + if (palette->colors[i].a == SDL_ALPHA_TRANSPARENT && !has_colorkey) { + has_colorkey = true; + colorkey_index = i; + } else { + /* Partial opacity or multiple colorkeys */ + has_alpha = true; + } + } + } + } + if (has_alpha) { + SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND); + } else if (has_colorkey) { + SDL_SetSurfaceColorKey(surface, true, colorkey_index); + } + + /* FIXME: This sucks. It'd be better to allocate the surface first, then + * write directly to the pixel buffer: + * https://github.com/nothings/stb/issues/58 + * -flibit + */ + surface->flags &= ~SDL_SURFACE_PREALLOCATED; + } + + } else if (format == STBI_grey || format == STBI_rgb || format == STBI_rgb_alpha) { + surface = SDL_CreateSurfaceFrom( + w, + h, + (format == STBI_rgb_alpha) ? SDL_PIXELFORMAT_RGBA32 : + (format == STBI_rgb) ? SDL_PIXELFORMAT_RGB24 : + SDL_PIXELFORMAT_INDEX8, + pixels, + w * format + ); + if (surface) { + /* Set a grayscale palette for gray images */ + if (surface->format == SDL_PIXELFORMAT_INDEX8) { + SDL_Palette *palette = SDL_CreateSurfacePalette(surface); + if (palette) { + int i; + + for (i = 0; i < palette->ncolors; i++) { + palette->colors[i].r = (Uint8)i; + palette->colors[i].g = (Uint8)i; + palette->colors[i].b = (Uint8)i; + } + } + } + + /* FIXME: This sucks. It'd be better to allocate the surface first, then + * write directly to the pixel buffer: + * https://github.com/nothings/stb/issues/58 + * -flibit + */ + surface->flags &= ~SDL_SURFACE_PREALLOCATED; + } + + } else if (format == STBI_grey_alpha) { + surface = SDL_CreateSurface(w, h, SDL_PIXELFORMAT_RGBA32); + if (surface) { + Uint8 *src_ptr = pixels; + Uint8 *dst = (Uint8 *)surface->pixels; + int skip = surface->pitch - (surface->w * 4); + int row, col; + + for (row = 0; row < h; ++row) { + for (col = 0; col < w; ++col) { + Uint8 c = *src_ptr++; + Uint8 a = *src_ptr++; + *dst++ = c; + *dst++ = c; + *dst++ = c; + *dst++ = a; + } + dst += skip; + } + stbi_image_free(pixels); + } + } else { + SDL_SetError("Unknown image format: %d", format); + } + + if (!surface) { + /* The error message should already be set */ + stbi_image_free(pixels); /* calls SDL_free() */ + SDL_SeekIO(src, start, SDL_IO_SEEK_SET); + } + return surface; +} +#endif // SDL_HAVE_STB + +SDL_Surface *SDL_LoadPNG_IO(SDL_IOStream *src, bool closeio) +{ + Sint64 start; + Uint8 magic[4]; + bool is_PNG; + SDL_Surface *surface = NULL; + + CHECK_PARAM(!src) { + SDL_InvalidParamError("src"); + 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) { + SDL_SetError("File is not a PNG 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_LoadPNG(const char *file) +{ + SDL_IOStream *stream = SDL_IOFromFile(file, "rb"); + if (!stream) { + return NULL; + } + + return SDL_LoadPNG_IO(stream, true); +} + #ifdef SDL_HAVE_STB static void SDL_STBWriteFunc(void *context, void *data, int size) { @@ -136,46 +381,46 @@ static void SDL_STBWriteFunc(void *context, void *data, int size) bool SDL_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio) { -#ifdef SDL_HAVE_STB - bool retval = true; + bool retval = false; // Make sure we have somewhere to save CHECK_PARAM(!SDL_SurfaceValid(surface)) { - retval = SDL_InvalidParamError("surface"); + SDL_InvalidParamError("surface"); goto done; } CHECK_PARAM(!dst) { - retval = SDL_InvalidParamError("dst"); + SDL_InvalidParamError("dst"); goto done; } +#ifdef SDL_HAVE_STB bool free_surface = false; - if (surface->format != SDL_PIXELFORMAT_ABGR8888) { - surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ABGR8888); + if (surface->format != SDL_PIXELFORMAT_RGBA32) { + surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32); if (!surface) { - retval = false; goto done; } free_surface = true; } - if (!stbi_write_png_to_func(SDL_STBWriteFunc, dst, surface->w, surface->h, 4, surface->pixels, surface->pitch)) { - retval = SDL_SetError("Failed to write PNG"); + if (stbi_write_png_to_func(SDL_STBWriteFunc, dst, surface->w, surface->h, 4, surface->pixels, surface->pitch)) { + retval = true; + } else { + SDL_SetError("Failed to write PNG"); } if (free_surface) { SDL_DestroySurface(surface); } +#else + SDL_SetError("SDL not built with STB image support"); +#endif done: if (dst && closeio) { - retval = SDL_CloseIO(dst); + retval &= SDL_CloseIO(dst); } - return retval; -#else - return SDL_SetError("SDL not built with STB image write support"); -#endif } bool SDL_SavePNG(SDL_Surface *surface, const char *file) @@ -188,6 +433,6 @@ bool SDL_SavePNG(SDL_Surface *surface, const char *file) return SDL_SavePNG_IO(surface, stream, true); #else - return SDL_SetError("SDL not built with STB image write support"); + return SDL_SetError("SDL not built with STB image support"); #endif } diff --git a/src/video/SDL_stb_c.h b/src/video/SDL_stb_c.h index 7ae541380a..bf8cc4da53 100644 --- a/src/video/SDL_stb_c.h +++ b/src/video/SDL_stb_c.h @@ -27,7 +27,5 @@ // Image conversion functions extern bool SDL_ConvertPixels_STB(int width, int height, SDL_PixelFormat src_format, SDL_Colorspace src_colorspace, SDL_PropertiesID src_properties, const void *src, int src_pitch, SDL_PixelFormat dst_format, SDL_Colorspace dst_colorspace, SDL_PropertiesID dst_properties, void *dst, int dst_pitch); -extern bool SDL_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio); -extern bool SDL_SavePNG(SDL_Surface *surface, const char *file); #endif // SDL_stb_c_h_ diff --git a/src/video/stb_image.h b/src/video/stb_image.h index 6c735a8187..1ce63899ec 100644 --- a/src/video/stb_image.h +++ b/src/video/stb_image.h @@ -453,8 +453,8 @@ STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wch #if 0 /* not used in SDL */ STBIDEF stbi_uc *stbi_load_from_memory_with_palette (stbi_uc const *buffer, int len , int *x, int *y, unsigned int *palette_buffer, int palette_buffer_len); -STBIDEF stbi_uc *stbi_load_from_callbacks_with_palette(stbi_io_callbacks const *clbk, void *user, int *x, int *y, unsigned int *palette_buffer, int palette_buffer_len); #endif +STBIDEF stbi_uc *stbi_load_from_callbacks_with_palette(stbi_io_callbacks const *clbk, void *user, int *x, int *y, unsigned int *palette_buffer, int palette_buffer_len); //////////////////////////////////// // @@ -559,14 +559,18 @@ STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_fli // ZLIB client - used by PNG, available for other purposes #ifndef STBI_NO_ZLIB +#if 0 /* not used in SDL */ STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +#endif STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +#if 0 /* not used in SDL */ STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); #endif +#endif #ifdef __cplusplus @@ -897,7 +901,6 @@ static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; } -#if 0 /* not used in SDL */ // initialize a callback-based context static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) { @@ -910,7 +913,6 @@ static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void * stbi__refill_buffer(s); s->img_buffer_original_end = s->img_buffer_end; } -#endif #ifndef STBI_NO_STDIO @@ -1353,7 +1355,6 @@ static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int byt } #endif -#if 0 /* not used in SDL */ static unsigned char *stbi__load_indexed(stbi__context *s, int *x, int *y, unsigned int *palette_buffer, int palette_buffer_len) { stbi__result_info ri; @@ -1386,7 +1387,6 @@ static unsigned char *stbi__load_indexed(stbi__context *s, int *x, int *y, unsig return (unsigned char *) result; } -#endif /**/ static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) { @@ -1568,7 +1568,6 @@ STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, i return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); } -#if 0 /* not used in SDL */ STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) { stbi__context s; @@ -1576,12 +1575,14 @@ STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *u return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); } +#if 0 /* not used in SDL */ STBIDEF stbi_uc *stbi_load_from_memory_with_palette(stbi_uc const *buffer, int len, int *x, int *y, unsigned int *palette_buffer, int palette_buffer_len) { stbi__context s; stbi__start_mem(&s, buffer, len); return stbi__load_indexed(&s, x, y, palette_buffer, palette_buffer_len); } +#endif STBIDEF stbi_uc *stbi_load_from_callbacks_with_palette(stbi_io_callbacks const *clbk, void *user, int *x, int *y, unsigned int *palette_buffer, int palette_buffer_len) { @@ -1589,7 +1590,6 @@ STBIDEF stbi_uc *stbi_load_from_callbacks_with_palette(stbi_io_callbacks const * stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); return stbi__load_indexed(&s, x, y, palette_buffer, palette_buffer_len); } -#endif #ifndef STBI_NO_GIF STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) @@ -4735,6 +4735,7 @@ static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse return stbi__parse_zlib(a, parse_header); } +#if 0 /* not used in SDL */ STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) { stbi__zbuf a; @@ -4755,6 +4756,7 @@ STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) { return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); } +#endif /* */ STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) { @@ -4772,6 +4774,7 @@ STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, i } } +#if 0 /* not used in SDL */ STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) { stbi__zbuf a; @@ -4809,6 +4812,7 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char else return -1; } +#endif /* */ #endif // public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 diff --git a/test/testsymbols.c b/test/testsymbols.c index a919b23300..7b5a9df2ed 100644 --- a/test/testsymbols.c +++ b/test/testsymbols.c @@ -1335,6 +1335,10 @@ const static struct { SDL_SYMBOL_ITEM(SDL_SetTexturePalette), SDL_SYMBOL_ITEM(SDL_GetTexturePalette), SDL_SYMBOL_ITEM(SDL_GetGPURendererDevice), + SDL_SYMBOL_ITEM(SDL_LoadPNG_IO), + SDL_SYMBOL_ITEM(SDL_LoadPNG), + SDL_SYMBOL_ITEM(SDL_SavePNG_IO), + SDL_SYMBOL_ITEM(SDL_SavePNG), /* extra symbols go here (don't modify this line) */ { NULL, NULL } };