Files
cell-image/gif.c
2026-02-17 09:11:52 -06:00

75 lines
2.8 KiB
C

#include "cell.h"
#include <string.h>
#include <stdlib.h>
#include "stb_image_common.h"
// GIF decoding via stb_image - handles animated GIFs
// Returns an object with:
// .width, .height - dimensions
// .frames - array of frame objects, each with:
// .width, .height, .format, .pitch, .pixels, .depth, .hdr
// .duration - frame duration in milliseconds (for animated GIFs)
// .frame_count - number of frames
JSValue js_gif_decode(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
{
size_t len;
void *raw = js_get_blob_data(js, &len, argv[0]);
if (raw == NULL) return JS_EXCEPTION;
if (!raw) return JS_ThrowReferenceError(js, "could not get GIF data from blob");
int n, width, height, frames_count;
int *delays = NULL;
void *data = stbi_load_gif_from_memory(raw, len, &delays, &width, &height, &frames_count, &n, 4);
if (!data)
return JS_ThrowReferenceError(js, "failed to decode GIF: %s", stbi_failure_reason());
if (width <= 0 || height <= 0) {
stbi_image_free(data);
if (delays) stbi_image_free(delays);
return JS_ThrowReferenceError(js, "decoded GIF has invalid size: %dx%d", width, height);
}
int pitch = width * 4;
size_t frame_size = pitch * height;
JS_FRAME(js);
JS_ROOT(frames_array, JS_NewArray(js));
for (int i = 0; i < frames_count; i++) {
JS_ROOT(frame, JS_NewObject(js));
JS_SetPropertyStr(js, frame.val, "width", JS_NewInt32(js, width));
JS_SetPropertyStr(js, frame.val, "height", JS_NewInt32(js, height));
JS_SetPropertyStr(js, frame.val, "format", JS_NewString(js, "rgba32"));
JS_SetPropertyStr(js, frame.val, "pitch", JS_NewInt32(js, pitch));
void *frame_pixels = (unsigned char*)data + (frame_size * i);
JS_SetPropertyStr(js, frame.val, "pixels", js_new_blob_stoned_copy(js, frame_pixels, frame_size));
JS_SetPropertyStr(js, frame.val, "depth", JS_NewInt32(js, 8));
JS_SetPropertyStr(js, frame.val, "hdr", JS_NewBool(js, 0));
// Add duration in milliseconds (delays are in centiseconds, i.e. 1/100th of a second)
int duration_ms = delays ? delays[i] * 10 : 100;
JS_SetPropertyStr(js, frame.val, "duration", JS_NewInt32(js, duration_ms));
JS_SetPropertyNumber(js, frames_array.val, i, frame.val);
}
// Create result object
JS_ROOT(result, JS_NewObject(js));
JS_SetPropertyStr(js, result.val, "width", JS_NewInt32(js, width));
JS_SetPropertyStr(js, result.val, "height", JS_NewInt32(js, height));
JS_SetPropertyStr(js, result.val, "frames", frames_array.val);
JS_SetPropertyStr(js, result.val, "frame_count", JS_NewInt32(js, frames_count));
stbi_image_free(data);
if (delays) stbi_image_free(delays);
JS_RETURN(result.val);
}
static const JSCFunctionListEntry js_gif_funcs[] = {
MIST_FUNC_DEF(gif, decode, 1)
};
CELL_USE_FUNCS(js_gif_funcs)