diff --git a/src/settings.js b/src/settings.js index b21cc9ddc2454..8e90212cc345e 100644 --- a/src/settings.js +++ b/src/settings.js @@ -1624,6 +1624,11 @@ var SDL2_IMAGE_FORMATS = []; // [link] var SDL2_MIXER_FORMATS = ["ogg"]; +// Formats to support in SDL3_image. Valid values: bmp, gif, lbm, pcx, png, pnm, +// tga, webp, xcf, xpm, xv +// [link] +var SDL3_IMAGE_FORMATS = []; + // 1 = use sqlite3 from emscripten-ports // Alternate syntax: --use-port=sqlite3 // [compile+link] diff --git a/test/browser/test_sdl3_image.c b/test/browser/test_sdl3_image.c new file mode 100644 index 0000000000000..74622a5289409 --- /dev/null +++ b/test/browser/test_sdl3_image.c @@ -0,0 +1,78 @@ +/* + * Copyright 2025 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifndef BITSPERPIXEL +#define BITSPERPIXEL 32 +#endif + +int testImage(SDL_Renderer* renderer, const char* fileName) { + SDL_Surface *image = IMG_Load(fileName); + if (!image) + { + printf("IMG_Load: %s\n", SDL_GetError()); + return 0; + } + assert(SDL_GetPixelFormatDetails(image->format)->bits_per_pixel == BITSPERPIXEL); + assert(SDL_GetPixelFormatDetails(image->format)->bytes_per_pixel == BITSPERPIXEL / 8); + assert(image->pitch == BITSPERPIXEL / 8 * image->w); + int result = image->w; + +#ifndef NO_PRELOADED + int w, h; + char *data = emscripten_get_preloaded_image_data(fileName, &w, &h); + + assert(data); + assert(w == image->w); + assert(h == image->h); +#endif + + SDL_Texture *tex = SDL_CreateTextureFromSurface(renderer, image); + + SDL_RenderTexture(renderer, tex, NULL, NULL); + + SDL_DestroyTexture(tex); + + SDL_DestroySurface(image); + +#ifndef NO_PRELOADED + free(data); +#endif + + return result; +} + +int main() { + SDL_Init(SDL_INIT_VIDEO); + + SDL_Window *window; + SDL_Renderer *renderer; + + SDL_CreateWindowAndRenderer("Test Window", 600, 450, 0, &window, &renderer); + + int result = 0; + + result = testImage(renderer, SCREENSHOT_DIRNAME "/" SCREENSHOT_BASENAME); // absolute path + assert(result != 0); + + chdir(SCREENSHOT_DIRNAME); + result = testImage(renderer, "./" SCREENSHOT_BASENAME); // relative path + assert(result != 0); + + SDL_RenderPresent(renderer); + + printf("you should see an image.\n"); + + return result; +} diff --git a/test/test_browser.py b/test/test_browser.py index a934191bc7ddb..9e9650b98f6f1 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -2992,6 +2992,45 @@ def test_sdl2_image_formats(self): '--use-port=sdl2', '--use-port=sdl2_image:formats=jpg', ]) + @requires_graphics_hardware + def test_sdl3_image(self): + # Same test as for SDL2, but with SDL3. + shutil.copy(test_file('screenshot.jpg'), '.') + + for dest, dirname, basename in [('screenshot.jpg', '/', 'screenshot.jpg'), + ('screenshot.jpg@/assets/screenshot.jpg', '/assets', 'screenshot.jpg')]: + self.btest_exit('test_sdl3_image.c', 600, cflags=[ + '-O2', + '--preload-file', dest, + '-DSCREENSHOT_DIRNAME="' + dirname + '"', + '-DSCREENSHOT_BASENAME="' + basename + '"', + '-sUSE_SDL=3', '-sUSE_SDL_IMAGE=3', '--use-preload-plugins', + ]) + + @requires_graphics_hardware + def test_sdl3_image_jpeg(self): + shutil.copy(test_file('screenshot.jpg'), 'screenshot.jpeg') + self.btest_exit('test_sdl3_image.c', 600, cflags=[ + '--preload-file', 'screenshot.jpeg', + '-DSCREENSHOT_DIRNAME="/"', '-DSCREENSHOT_BASENAME="screenshot.jpeg"', + '-sUSE_SDL=3', '-sUSE_SDL_IMAGE=3', '--use-preload-plugins', + ]) + + @requires_graphics_hardware + def test_sdl3_image_formats(self): + shutil.copy(test_file('screenshot.png'), '.') + shutil.copy(test_file('screenshot.jpg'), '.') + self.btest_exit('test_sdl3_image.c', 512, cflags=[ + '--preload-file', 'screenshot.png', + '-DSCREENSHOT_DIRNAME="/"', '-DSCREENSHOT_BASENAME="screenshot.png"', '-DNO_PRELOADED', + '-sUSE_SDL=3', '-sUSE_SDL_IMAGE=3', '-sSDL3_IMAGE_FORMATS=png', + ]) + self.btest_exit('test_sdl3_image.c', 600, cflags=[ + '--preload-file', 'screenshot.jpg', + '-DSCREENSHOT_DIRNAME="/"', '-DSCREENSHOT_BASENAME="screenshot.jpg"', '-DBITSPERPIXEL=24', '-DNO_PRELOADED', + '--use-port=sdl3', '--use-port=sdl3_image:formats=jpg', + ]) + def test_sdl2_key(self): self.btest_exit('test_sdl2_key.c', 37182145, cflags=['-sUSE_SDL=2', '--pre-js', test_file('browser/fake_events.js')]) diff --git a/tools/ports/sdl3_image.py b/tools/ports/sdl3_image.py new file mode 100644 index 0000000000000..75a0a1f31594d --- /dev/null +++ b/tools/ports/sdl3_image.py @@ -0,0 +1,136 @@ +# Copyright 2025 The Emscripten Authors. All rights reserved. +# Emscripten is available under two separate licenses, the MIT license and the +# University of Illinois/NCSA Open Source License. Both these licenses can be +# found in the LICENSE file. + +import os + +from typing import Dict, Set + +TAG = 'release-3.2.4' +HASH = '102b8ea30506a1aa0a23196d807e5cdf1ff9efcbcf9db2518a3fffef82f94bda96891129c24a8008a4ec374c9dfc39d5321cf7e573f3e3b10e85b2cfbbc1ad9b' + +deps = ['sdl3'] +variants = { + 'sdl3_image-jpg': {'SDL3_IMAGE_FORMATS': ["jpg"]}, + 'sdl3_image-png': {'SDL3_IMAGE_FORMATS': ["png"]}, + 'sdl3_image-jpg-mt': {'SDL3_IMAGE_FORMATS': ["jpg"], 'PTHREADS': 1}, + 'sdl3_image-png-mt': {'SDL3_IMAGE_FORMATS': ["png"], 'PTHREADS': 1}, +} + +OPTIONS = { + 'formats': 'A comma separated list of formats (ex: --use-port=sdl3_image:formats=png,jpg)', +} + +SUPPORTED_FORMATS = {'avif', 'bmp', 'gif', 'jpg', 'jxl', 'lbm', 'pcx', 'png', + 'pnm', 'qoi', 'svg', 'tga', 'tif', 'webp', 'xcf', 'xpm', 'xv'} + +# user options (from --use-port) +opts: Dict[str, Set] = { + 'formats': set(), +} + + +def needed(settings): + return settings.USE_SDL_IMAGE == 3 + + +def get_formats(settings): + return opts['formats'].union(settings.SDL3_IMAGE_FORMATS) + + +def get_lib_name(settings): + formats = '-'.join(sorted(get_formats(settings))) + + libname = 'libSDL3_image' + if formats != '': + libname += '-' + formats + if settings.PTHREADS: + libname += '-mt' + if settings.SUPPORT_LONGJMP == 'wasm': + libname += '-wasm-sjlj' + return libname + '.a' + + +def get(ports, settings, shared): + ports.fetch_project('sdl3_image', f'https://github.com/libsdl-org/SDL_image/archive/{TAG}.zip', sha512hash=HASH) + libname = get_lib_name(settings) + + def create(final): + src_root = ports.get_dir('sdl3_image', 'SDL_image-' + TAG) + ports.install_header_dir(os.path.join(src_root, 'include'), target='.') + srcs = [ + "src/IMG.c", + "src/IMG_ImageIO.m", + "src/IMG_WIC.c", + "src/IMG_avif.c", + "src/IMG_bmp.c", + "src/IMG_gif.c", + "src/IMG_jpg.c", + "src/IMG_jxl.c", + "src/IMG_lbm.c", + "src/IMG_pcx.c", + "src/IMG_png.c", + "src/IMG_pnm.c", + "src/IMG_qoi.c", + "src/IMG_stb.c", + "src/IMG_svg.c", + "src/IMG_tga.c", + "src/IMG_tif.c", + "src/IMG_webp.c", + "src/IMG_xcf.c", + "src/IMG_xpm.c", + "src/IMG_xv.c", + "src/IMG_xxx.c", + ] + + flags = ['-sUSE_SDL=3', '-Wno-format-security'] + + formats = get_formats(settings) + + flags.extend(f'-DLOAD_{fmt.upper()}' for fmt in formats) + + if 'png' in formats: + flags += ['-sUSE_LIBPNG'] + + if 'jpg' in formats: + flags += ['-sUSE_LIBJPEG'] + + if settings.PTHREADS: + flags += ['-pthread'] + + if settings.SUPPORT_LONGJMP == 'wasm': + flags.append('-sSUPPORT_LONGJMP=wasm') + + ports.build_port(src_root, final, 'sdl3_image', flags=flags, srcs=srcs) + + return [shared.cache.get_lib(libname, create, what='port')] + + +def clear(ports, settings, shared): + shared.cache.erase_lib(get_lib_name(settings)) + + +def process_dependencies(settings): + settings.USE_SDL = 3 + formats = get_formats(settings) + if 'png' in formats: + deps.append('libpng') + settings.USE_LIBPNG = 1 + if 'jpg' in formats: + deps.append('libjpeg') + settings.USE_LIBJPEG = 1 + + +def handle_options(options, error_handler): + formats = options['formats'].split(',') + for format in formats: + format = format.lower().strip() + if format not in SUPPORTED_FORMATS: + error_handler(f'{format} is not a supported format') + else: + opts['formats'].add(format) + + +def show(): + return 'sdl3_image (-sUSE_SDL_IMAGE=3 or --use-port=sdl3_image; zlib license)' diff --git a/tools/settings.py b/tools/settings.py index 8e640c6a5d41f..822977bb31b4f 100644 --- a/tools/settings.py +++ b/tools/settings.py @@ -53,6 +53,7 @@ 'USE_FREETYPE', 'SDL2_MIXER_FORMATS', 'SDL2_IMAGE_FORMATS', + 'SDL3_IMAGE_FORMATS', 'USE_SQLITE3', }