diff --git a/examples/convert.ce b/examples/convert.ce index dee0635..c27a83b 100644 --- a/examples/convert.ce +++ b/examples/convert.ce @@ -24,7 +24,7 @@ if (args.length < 2) { log.console("Example: cell run convert.ce lenna.png lenna.qoi") log.console("Decode: png, jpg, bmp, tga, gif, psd, qoi, ase/aseprite") log.console("Encode: png, jpg, bmp, tga, qoi") - $_.stop() + $stop() } var input_file = args[0] @@ -77,14 +77,14 @@ log.console("Loading input image: " + input_file) var input_blob = io.slurp(input_file) if (!input_blob) { log.console("Error: Could not load input file: " + input_file) - $_.stop() + $stop() } var input_ext = ext_lower(input_file) var decoded = decode_image(input_blob, input_ext) if (!decoded) { log.console("Error: Failed to decode image: " + input_file) - $_.stop() + $stop() } log.console("Image loaded: " + text(decoded.width) + "x" + text(decoded.height) + " pixels") @@ -92,7 +92,7 @@ log.console("Image loaded: " + text(decoded.width) + "x" + text(decoded.height) var output_ext = ext_lower(output_file) if (!output_ext) { log.console("Error: Could not determine output format from: " + output_file) - $_.stop() + $stop() } log.console("Converting to format: " + output_ext) @@ -100,13 +100,13 @@ log.console("Converting to format: " + output_ext) var encoded = encode_image(decoded, output_ext) if (!encoded) { log.console("Error: Failed to encode image to format: " + output_ext) - $_.stop() + $stop() } var success = io.slurpwrite(output_file, encoded.pixels) if (!success) { log.console("Error: Could not save output file: " + output_file) - $_.stop() + $stop() } log.console("Successfully converted " + input_file + " to " + output_file) diff --git a/gif.c b/gif.c index 8e264a7..0a3143e 100644 --- a/gif.c +++ b/gif.c @@ -3,7 +3,13 @@ #include #include "stb_image_common.h" -// GIF decoding via stb_image (returns first frame only for animated GIFs) +// 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; @@ -11,31 +17,54 @@ JSValue js_gif_decode(JSContext *js, JSValue this_val, int argc, JSValueConst *a if (raw == NULL) return JS_EXCEPTION; if (!raw) return JS_ThrowReferenceError(js, "could not get GIF data from blob"); - int n, width, height; - void *data = stbi_load_from_memory(raw, len, &width, &height, &n, 4); + 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 pixels_size = pitch * height; + size_t frame_size = pitch * height; - JSValue obj = JS_NewObject(js); - JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, width)); - JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, height)); - JS_SetPropertyStr(js, obj, "format", JS_NewString(js, "rgba32")); - JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, pitch)); - JS_SetPropertyStr(js, obj, "pixels", js_new_blob_stoned_copy(js, data, pixels_size)); - JS_SetPropertyStr(js, obj, "depth", JS_NewInt32(js, 8)); - JS_SetPropertyStr(js, obj, "hdr", JS_NewBool(js, 0)); + // Create frames array + JSValue frames_array = JS_NewArray(js); + + for (int i = 0; i < frames_count; i++) { + JSValue frame = JS_NewObject(js); + JS_SetPropertyStr(js, frame, "width", JS_NewInt32(js, width)); + JS_SetPropertyStr(js, frame, "height", JS_NewInt32(js, height)); + JS_SetPropertyStr(js, frame, "format", JS_NewString(js, "rgba32")); + JS_SetPropertyStr(js, frame, "pitch", JS_NewInt32(js, pitch)); + + void *frame_pixels = (unsigned char*)data + (frame_size * i); + JS_SetPropertyStr(js, frame, "pixels", js_new_blob_stoned_copy(js, frame_pixels, frame_size)); + JS_SetPropertyStr(js, frame, "depth", JS_NewInt32(js, 8)); + JS_SetPropertyStr(js, frame, "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, "duration", JS_NewInt32(js, duration_ms)); + + JS_SetPropertyUint32(js, frames_array, i, frame); + } + + // Create result object + JSValue result = JS_NewObject(js); + JS_SetPropertyStr(js, result, "width", JS_NewInt32(js, width)); + JS_SetPropertyStr(js, result, "height", JS_NewInt32(js, height)); + JS_SetPropertyStr(js, result, "frames", frames_array); + JS_SetPropertyStr(js, result, "frame_count", JS_NewInt32(js, frames_count)); stbi_image_free(data); - return obj; + if (delays) stbi_image_free(delays); + return result; } static const JSCFunctionListEntry js_gif_funcs[] = {