AtlatestRepositorysigil-graphics
sigil-graphics / tree / src / cimage.c
1
/*2
* image.c - CPU-side image loading for Sigil Graphics3
*4
* Provides image loading using stb_image. Images are loaded into CPU memory5
* and can be inspected or converted to GPU textures via (sigil graphics).6
*/8
#include "sigil-internal.h"9
#include <stdio.h>10
#include <stdlib.h>11
#include <string.h>13
#include "stb_image.h"15
/* Image type tag (initialized at module init) */16
static Value image_type_tag = SIGIL_UNDEFINED;18
/* Image structure stored as foreign object */19
typedef struct {20
unsigned char *pixels; /* RGBA pixel data */21
int width;22
int height;23
int channels; /* Always 4 (RGBA) after loading */24
} StudioImage;26
/* Initialize image type tag */27
static void ensure_image_type(SigilVM *vm)28
{29
if (sigil_is_undefined(image_type_tag)) {30
image_type_tag = sigil_intern_symbol(vm, "sigil-graphics-image", 20);31
}32
}34
/* Get image from Value, returns NULL if not an image */35
static StudioImage *get_image(SigilVM *vm, Value v)36
{37
if (!sigil_is_foreign(v)) return NULL;38
ensure_image_type(vm);39
if (sigil_foreign_type(v) != image_type_tag) return NULL;40
return (StudioImage *)sigil_foreign_data(v);41
}43
/* Destructor for image foreign object */44
static void image_destructor(void *data)45
{46
StudioImage *img = (StudioImage *)data;47
if (img) {48
if (img->pixels) {49
stbi_image_free(img->pixels);50
}51
free(img);52
}53
}55
/*56
* (load-image path) -> <image> or #f57
*58
* Load an image file from disk. Returns an image object or #f on failure.59
* Supported formats: PNG, JPEG, BMP, TGA, GIF, HDR, PSD, PIC, PNM60
*/61
static Value native_load_image(SigilVM *vm, int argc, Value *args)62
{63
(void)argc;65
if (!sigil_is_string(args[0])) {66
sigil__vm_set_error(vm, SIGIL_ERR_TYPE, "load-image: expected string path");67
return SIGIL_FALSE;68
}70
SigilString *path_str = (SigilString *)sigil_as_ptr(args[0]);71
const char *path = path_str->data;73
int width, height, channels;74
/* Always request 4 channels (RGBA) for consistency */75
unsigned char *pixels = stbi_load(path, &width, &height, &channels, 4);77
if (!pixels) {78
/* Return #f on failure - don't set error, let caller handle it */79
return SIGIL_FALSE;80
}82
/* Create image structure */83
StudioImage *img = malloc(sizeof(StudioImage));84
if (!img) {85
stbi_image_free(pixels);86
sigil__vm_set_error(vm, SIGIL_ERR_MEMORY, "load-image: out of memory");87
return SIGIL_FALSE;88
}90
img->pixels = pixels;91
img->width = width;92
img->height = height;93
img->channels = 4; /* We always load as RGBA */95
/* Wrap in foreign object */96
ensure_image_type(vm);97
return sigil_make_foreign(vm, image_type_tag, img, image_destructor,98
sizeof(StudioImage) + (size_t)(width * height * 4));99
}101
/*102
* (load-image-from-memory ptr len) -> <image> or #f103
*104
* Decode an image from raw ENCODED bytes already resident in WASM linear memory105
* at address `ptr` (a fixnum), `len` bytes long, using stb_image's from-memory106
* decoder. On web there is no filesystem: JS fetches the image file and copies107
* the encoded bytes into memory (see the sigil-wasm-gles3 app-shell), and this108
* 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 no111
* existing behaviour (stbi_load_from_memory is core stb_image); it is exported112
* unconditionally because the module's (export) list must match the C113
* registration on both platforms.114
*/115
static Value native_load_image_from_memory(SigilVM *vm, int argc, Value *args)116
{117
(void)argc;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
}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;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;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
}140
img->pixels = pixels;141
img->width = width;142
img->height = height;143
img->channels = 4;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
}150
/*151
* (image? obj) -> boolean152
*153
* Check if object is an image.154
*/155
static Value native_image_p(SigilVM *vm, int argc, Value *args)156
{157
(void)argc;158
return get_image(vm, args[0]) ? SIGIL_TRUE : SIGIL_FALSE;159
}161
/*162
* (image-width img) -> integer163
*164
* Get image width in pixels.165
*/166
static Value native_image_width(SigilVM *vm, int argc, Value *args)167
{168
(void)argc;170
StudioImage *img = get_image(vm, args[0]);171
if (!img) {172
sigil__vm_set_error(vm, SIGIL_ERR_TYPE, "image-width: expected image");173
return SIGIL_UNDEFINED;174
}176
return sigil_fixnum(img->width);177
}179
/*180
* (image-height img) -> integer181
*182
* Get image height in pixels.183
*/184
static Value native_image_height(SigilVM *vm, int argc, Value *args)185
{186
(void)argc;188
StudioImage *img = get_image(vm, args[0]);189
if (!img) {190
sigil__vm_set_error(vm, SIGIL_ERR_TYPE, "image-height: expected image");191
return SIGIL_UNDEFINED;192
}194
return sigil_fixnum(img->height);195
}197
/*198
* (image-channels img) -> integer199
*200
* Get number of channels (always 4 for RGBA).201
*/202
static Value native_image_channels(SigilVM *vm, int argc, Value *args)203
{204
(void)argc;206
StudioImage *img = get_image(vm, args[0]);207
if (!img) {208
sigil__vm_set_error(vm, SIGIL_ERR_TYPE, "image-channels: expected image");209
return SIGIL_UNDEFINED;210
}212
return sigil_fixnum(img->channels);213
}215
/*216
* (image-free! img) -> void217
*218
* Free the CPU-side pixel data. The image object becomes invalid.219
* This is optional - GC will clean up automatically, but this allows220
* explicit memory management for large images.221
*/222
static Value native_image_free(SigilVM *vm, int argc, Value *args)223
{224
(void)argc;226
StudioImage *img = get_image(vm, args[0]);227
if (!img) {228
sigil__vm_set_error(vm, SIGIL_ERR_TYPE, "image-free!: expected image");229
return SIGIL_UNDEFINED;230
}232
if (img->pixels) {233
stbi_image_free(img->pixels);234
img->pixels = NULL;235
img->width = 0;236
img->height = 0;237
}239
return SIGIL_NIL;240
}242
/*243
* Internal: Get pixel data pointer for use by graphics module.244
* Returns NULL if image is invalid or freed.245
*/246
unsigned char *sigil_graphics_image_pixels(SigilVM *vm, Value img_val, int *width, int *height)247
{248
StudioImage *img = get_image(vm, img_val);249
if (!img || !img->pixels) return NULL;251
if (width) *width = img->width;252
if (height) *height = img->height;253
return img->pixels;254
}256
/*257
* Module initialization258
*/259
void sigil__init_sigil_graphics_image_module(SigilVM *vm)260
{261
SigilModule *module = sigil_begin_module(vm, "(sigil graphics image)");262
if (!module) return;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,271
SIGIL_ARITY_EXACT(1), "Get image width");272
sigil_module_register_native(vm, "image-height", native_image_height,273
SIGIL_ARITY_EXACT(1), "Get image height");274
sigil_module_register_native(vm, "image-channels", native_image_channels,275
SIGIL_ARITY_EXACT(1), "Get number of channels");276
sigil_module_register_native(vm, "image-free!", native_image_free,277
SIGIL_ARITY_EXACT(1), "Free image pixel data");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");284
sigil_module_export(vm, "image-channels");285
sigil_module_export(vm, "image-free!");287
sigil_end_module(vm);288
}