From 1f582161bc5b31bf11e8d36d5f10c0f4aff12da3 Mon Sep 17 00:00:00 2001 From: Edgar J San Martin <29460583+ej-sanmartin@users.noreply.github.com> Date: Thu, 20 Nov 2025 02:14:55 -0500 Subject: [PATCH 1/3] emscripten: Add SDL_SetWindowIcon implementation. --- src/video/emscripten/SDL_emscriptenvideo.c | 54 +++++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/src/video/emscripten/SDL_emscriptenvideo.c b/src/video/emscripten/SDL_emscriptenvideo.c index bb7f7ba8e4553..6ccfadda37524 100644 --- a/src/video/emscripten/SDL_emscriptenvideo.c +++ b/src/video/emscripten/SDL_emscriptenvideo.c @@ -48,6 +48,7 @@ static void Emscripten_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) static SDL_FullscreenResult Emscripten_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen); static void Emscripten_PumpEvents(SDL_VideoDevice *_this); static void Emscripten_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); +static bool Emscripten_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon); static bool pumpevents_has_run = false; static int pending_swap_interval = -1; @@ -157,8 +158,8 @@ static SDL_VideoDevice *Emscripten_CreateDevice(void) device->CreateSDLWindow = Emscripten_CreateWindow; device->SetWindowTitle = Emscripten_SetWindowTitle; - /*device->SetWindowIcon = Emscripten_SetWindowIcon; - device->SetWindowPosition = Emscripten_SetWindowPosition;*/ + device->SetWindowIcon = Emscripten_SetWindowIcon; + /*device->SetWindowPosition = Emscripten_SetWindowPosition;*/ device->SetWindowSize = Emscripten_SetWindowSize; device->SetWindowResizable = Emscripten_SetWindowResizable; /*device->ShowWindow = Emscripten_ShowWindow; @@ -716,4 +717,53 @@ static void Emscripten_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window emscripten_set_window_title(window->title); } +static bool Emscripten_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon) +{ + // Icon is already converted to ARGB8888 by SDL_SetWindowIcon + SDL_assert(icon->format == SDL_PIXELFORMAT_ARGB8888); + + MAIN_THREAD_EM_ASM({ + var w = $0; + var h = $1; + var pitch = $2; + var pixelsPtr = $3; + + var canvas = document.createElement('canvas'); + canvas.width = w; + canvas.height = h; + var ctx = canvas.getContext('2d'); + var imageData = ctx.createImageData(w, h); + + // Copy pixels from SDL surface (ARGB8888) to ImageData (RGBA) + // ARGB8888 on little-endian: [B, G, R, A] bytes in memory + var src = HEAPU8.subarray(pixelsPtr, pixelsPtr + (h * pitch)); + for (var y = 0; y < h; y++) { + for (var x = 0; x < w; x++) { + var si = y * pitch + x * 4; // source index + var di = (y * w + x) * 4; // destination index + imageData.data[di + 0] = src[si + 2]; // R + imageData.data[di + 1] = src[si + 1]; // G + imageData.data[di + 2] = src[si + 0]; // B + imageData.data[di + 3] = src[si + 3]; // A + } + } + + ctx.putImageData(imageData, 0, 0); + + var dataUrl = canvas.toDataURL('image/png'); + + // Set as favicon (create link element if it doesn't exist) + var link = document.querySelector("link[rel~='icon']"); + if (!link) { + link = document.createElement('link'); + link.rel = 'icon'; + document.head.appendChild(link); + } + link.href = dataUrl; + + }, icon->w, icon->h, icon->pitch, icon->pixels); + + return true; +} + #endif // SDL_VIDEO_DRIVER_EMSCRIPTEN From d560479a0a1bec585118102a4e1e55124d46e77f Mon Sep 17 00:00:00 2001 From: Edgar J San Martin <29460583+ej-sanmartin@users.noreply.github.com> Date: Thu, 20 Nov 2025 12:31:49 -0500 Subject: [PATCH 2/3] Explicitly set link to image/type --- src/video/emscripten/SDL_emscriptenvideo.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/video/emscripten/SDL_emscriptenvideo.c b/src/video/emscripten/SDL_emscriptenvideo.c index 6ccfadda37524..67fe2a97f6bf3 100644 --- a/src/video/emscripten/SDL_emscriptenvideo.c +++ b/src/video/emscripten/SDL_emscriptenvideo.c @@ -757,6 +757,7 @@ static bool Emscripten_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, if (!link) { link = document.createElement('link'); link.rel = 'icon'; + link.type = 'image/png'; document.head.appendChild(link); } link.href = dataUrl; From 937e2362819a13e564daed93d1b037e1a84d5650 Mon Sep 17 00:00:00 2001 From: Edgar J San Martin <29460583+ej-sanmartin@users.noreply.github.com> Date: Thu, 20 Nov 2025 22:42:57 -0500 Subject: [PATCH 3/3] Update emscripten SDL_SetWindowIcon implementation to use native SDL_SavePNG_IO(). --- src/video/emscripten/SDL_emscriptenvideo.c | 61 +++++++++++----------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/src/video/emscripten/SDL_emscriptenvideo.c b/src/video/emscripten/SDL_emscriptenvideo.c index 67fe2a97f6bf3..dc3a9680905d1 100644 --- a/src/video/emscripten/SDL_emscriptenvideo.c +++ b/src/video/emscripten/SDL_emscriptenvideo.c @@ -719,38 +719,31 @@ static void Emscripten_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window static bool Emscripten_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon) { - // Icon is already converted to ARGB8888 by SDL_SetWindowIcon - SDL_assert(icon->format == SDL_PIXELFORMAT_ARGB8888); + // Create dynamic memory stream for PNG encoding + SDL_IOStream *stream = SDL_IOFromDynamicMem(); + if (!stream) { + return false; + } - MAIN_THREAD_EM_ASM({ - var w = $0; - var h = $1; - var pitch = $2; - var pixelsPtr = $3; - - var canvas = document.createElement('canvas'); - canvas.width = w; - canvas.height = h; - var ctx = canvas.getContext('2d'); - var imageData = ctx.createImageData(w, h); - - // Copy pixels from SDL surface (ARGB8888) to ImageData (RGBA) - // ARGB8888 on little-endian: [B, G, R, A] bytes in memory - var src = HEAPU8.subarray(pixelsPtr, pixelsPtr + (h * pitch)); - for (var y = 0; y < h; y++) { - for (var x = 0; x < w; x++) { - var si = y * pitch + x * 4; // source index - var di = (y * w + x) * 4; // destination index - imageData.data[di + 0] = src[si + 2]; // R - imageData.data[di + 1] = src[si + 1]; // G - imageData.data[di + 2] = src[si + 0]; // B - imageData.data[di + 3] = src[si + 3]; // A - } - } + // Encode icon to PNG using SDL's native PNG encoder + if (!SDL_SavePNG_IO(icon, stream, false)) { + SDL_CloseIO(stream); + return false; + } - ctx.putImageData(imageData, 0, 0); + // Get the PNG data from the stream + void *png_data = SDL_GetPointerProperty( + SDL_GetIOProperties(stream), + SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, + NULL + ); + size_t png_size = (size_t)SDL_GetIOSize(stream); - var dataUrl = canvas.toDataURL('image/png'); + // Pass PNG data to JavaScript + MAIN_THREAD_EM_ASM({ + var pngData = HEAPU8.subarray($0, $0 + $1); + var blob = new Blob([pngData], {type: 'image/png'}); + var url = URL.createObjectURL(blob); // Set as favicon (create link element if it doesn't exist) var link = document.querySelector("link[rel~='icon']"); @@ -760,10 +753,16 @@ static bool Emscripten_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, link.type = 'image/png'; document.head.appendChild(link); } - link.href = dataUrl; - }, icon->w, icon->h, icon->pitch, icon->pixels); + // Revoke old URL if it exists to prevent memory leaks + if (link.href && link.href.startsWith('blob:')) { + URL.revokeObjectURL(link.href); + } + + link.href = url; + }, png_data, png_size); + SDL_CloseIO(stream); return true; }