Commit9a4935d0Recorded12 Jul 2026Repositorysigil-graphics

graphics/image: add load-image-from-memory (decode encoded bytes) for web

Message

On the web there is no filesystem, so load-image's stbiload(path) can't be used. Add (load-image-from-memory ptr len): decode an image from raw encoded bytes already resident in linear memory (a wasm address + length), via stbimage's stbiloadfrom_memory, producing the same <image> object load-image returns from a path. The web build's sigil-wasm-gles3 app-shell fetches the file and copies its bytes into memory; this turns them into a GPU texture.

Purely ADDITIVE: a new procedure on native + web alike (stbiloadfrommemory is core stbimage). It changes no existing behaviour and removes no lines; it is exported unconditionally because the module's (export) list must match the C registration on both platforms. Native image.c compiles unchanged.

Changed
 src/c/image.c                | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/sigil/graphics/image.sgl |  1 +
 2 files changed, 53 insertions(+)
Diff
src/c/image.cmodified
@@ -98,6 +98,55 @@ static Value native_load_image(SigilVM *vm, int argc, Value *args)
98
sizeof(StudioImage) + (size_t)(width * height * 4));
99
}
100
+101
/*
+102
* (load-image-from-memory ptr len) -> <image> or #f
+103
*
+104
* Decode an image from raw ENCODED bytes already resident in WASM linear memory
+105
* at address `ptr` (a fixnum), `len` bytes long, using stb_image's from-memory
+106
* decoder. On web there is no filesystem: JS fetches the image file and copies
+107
* the encoded bytes into memory (see the sigil-wasm-gles3 app-shell), and this
+108
* turns them into the same <image> object load-image produces from a path.
+109
*
+110
* ADDITIVE: a new procedure available on native + web alike. It changes no
+111
* existing behaviour (stbi_load_from_memory is core stb_image); it is exported
+112
* unconditionally because the module's (export) list must match the C
+113
* registration on both platforms.
+114
*/
+115
static Value native_load_image_from_memory(SigilVM *vm, int argc, Value *args)
+116
{
+117
(void)argc;
+118
+119
if (!sigil_is_fixnum(args[0]) || !sigil_is_fixnum(args[1])) {
+120
sigil__vm_set_error(vm, SIGIL_ERR_TYPE,
+121
"load-image-from-memory: expected (ptr len) fixnums");
+122
return SIGIL_FALSE;
+123
}
+124
+125
const unsigned char *bytes = (const unsigned char *)(uintptr_t)sigil_as_fixnum(args[0]);
+126
int len = (int)sigil_as_fixnum(args[1]);
+127
if (!bytes || len <= 0) return SIGIL_FALSE;
+128
+129
int width, height, channels;
+130
unsigned char *pixels = stbi_load_from_memory(bytes, len, &width, &height, &channels, 4);
+131
if (!pixels) return SIGIL_FALSE;
+132
+133
StudioImage *img = malloc(sizeof(StudioImage));
+134
if (!img) {
+135
stbi_image_free(pixels);
+136
sigil__vm_set_error(vm, SIGIL_ERR_MEMORY, "load-image-from-memory: out of memory");
+137
return SIGIL_FALSE;
+138
}
+139
+140
img->pixels = pixels;
+141
img->width = width;
+142
img->height = height;
+143
img->channels = 4;
+144
+145
ensure_image_type(vm);
+146
return sigil_make_foreign(vm, image_type_tag, img, image_destructor,
+147
sizeof(StudioImage) + (size_t)(width * height * 4));
+148
}
+149
150
/*
151
* (image? obj) -> boolean
152
*
@@ -214,6 +263,8 @@ void sigil__init_sigil_graphics_image_module(SigilVM *vm)
263
264
sigil_module_register_native(vm, "load-image", native_load_image,
265
SIGIL_ARITY_EXACT(1), "Load image from file path");
+266
sigil_module_register_native(vm, "load-image-from-memory", native_load_image_from_memory,
+267
SIGIL_ARITY_EXACT(2), "Decode an image from encoded bytes in memory");
268
sigil_module_register_native(vm, "image?", native_image_p,
269
SIGIL_ARITY_EXACT(1), "Check if object is an image");
270
sigil_module_register_native(vm, "image-width", native_image_width,
@@ -226,6 +277,7 @@ void sigil__init_sigil_graphics_image_module(SigilVM *vm)
277
SIGIL_ARITY_EXACT(1), "Free image pixel data");
278
279
sigil_module_export(vm, "load-image");
+280
sigil_module_export(vm, "load-image-from-memory");
281
sigil_module_export(vm, "image?");
282
sigil_module_export(vm, "image-width");
283
sigil_module_export(vm, "image-height");
src/sigil/graphics/image.sglmodified
@@ -13,6 +13,7 @@
13
(export
14
;; Image loading
15
load-image
+16
load-image-from-memory
17
18
;; Image predicates and properties
19
image?