AtlatestRepositorysigil-graphics

sigil-graphics / tree / src / cimage.c

1/*
2 * image.c - CPU-side image loading for Sigil Graphics
3 *
4 * Provides image loading using stb_image. Images are loaded into CPU memory
5 * and can be inspected or converted to GPU textures via (sigil graphics).
6 */
7
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) */
16static Value image_type_tag = SIGIL_UNDEFINED;
18/* Image structure stored as foreign object */
19typedef 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 */
27static void ensure_image_type(SigilVM *vm)
29 if (sigil_is_undefined(image_type_tag)) {
30 image_type_tag = sigil_intern_symbol(vm, "sigil-graphics-image", 20);
31 }
34/* Get image from Value, returns NULL if not an image */
35static StudioImage *get_image(SigilVM *vm, Value v)
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);
43/* Destructor for image foreign object */
44static void image_destructor(void *data)
46 StudioImage *img = (StudioImage *)data;
47 if (img) {
48 if (img->pixels) {
49 stbi_image_free(img->pixels);
50 }
51 free(img);
52 }
55/*
56 * (load-image path) -> <image> or #f
57 *
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, PNM
60 */
61static Value native_load_image(SigilVM *vm, int argc, Value *args)
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));
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 */
115static Value native_load_image_from_memory(SigilVM *vm, int argc, Value *args)
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));
150/*
151 * (image? obj) -> boolean
152 *
153 * Check if object is an image.
154 */
155static Value native_image_p(SigilVM *vm, int argc, Value *args)
157 (void)argc;
158 return get_image(vm, args[0]) ? SIGIL_TRUE : SIGIL_FALSE;
161/*
162 * (image-width img) -> integer
163 *
164 * Get image width in pixels.
165 */
166static Value native_image_width(SigilVM *vm, int argc, Value *args)
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);
179/*
180 * (image-height img) -> integer
181 *
182 * Get image height in pixels.
183 */
184static Value native_image_height(SigilVM *vm, int argc, Value *args)
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);
197/*
198 * (image-channels img) -> integer
199 *
200 * Get number of channels (always 4 for RGBA).
201 */
202static Value native_image_channels(SigilVM *vm, int argc, Value *args)
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);
215/*
216 * (image-free! img) -> void
217 *
218 * Free the CPU-side pixel data. The image object becomes invalid.
219 * This is optional - GC will clean up automatically, but this allows
220 * explicit memory management for large images.
221 */
222static Value native_image_free(SigilVM *vm, int argc, Value *args)
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;
242/*
243 * Internal: Get pixel data pointer for use by graphics module.
244 * Returns NULL if image is invalid or freed.
245 */
246unsigned char *sigil_graphics_image_pixels(SigilVM *vm, Value img_val, int *width, int *height)
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;
256/*
257 * Module initialization
258 */
259void sigil__init_sigil_graphics_image_module(SigilVM *vm)
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);