#include "cell.h" #include #include #define CUTE_ASEPRITE_IMPLEMENTATION #include "cute_aseprite.h" // Helper function to check for integer overflow in size calculations static int check_size_overflow(size_t a, size_t b, size_t c, size_t *result) { if (a > SIZE_MAX / b) return 1; size_t temp = a * b; if (temp > SIZE_MAX / c) return 1; *result = temp * c; return 0; } // Helper function to convert cute_aseprite frame to JS image object static JSValue frame_to_js(JSContext *js, ase_frame_t *frame, int width, int height) { // Create JS object with frame data 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, width * 4)); JS_SetPropertyStr(js, obj, "depth", JS_NewInt32(js, 8)); JS_SetPropertyStr(js, obj, "hdr", JS_NewBool(js, 0)); // Copy pixel data size_t pixels_size = width * height * 4; JSValue pixels = js_new_blob_stoned_copy(js, frame->pixels, pixels_size); JS_SetPropertyStr(js, obj, "pixels", pixels); // Add frame duration (in milliseconds) JS_SetPropertyStr(js, obj, "duration", JS_NewInt32(js, frame->duration_milliseconds)); return obj; } // Aseprite decoding JSValue js_aseprite_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 Aseprite data from array buffer"); ase_t *ase = cute_aseprite_load_from_memory(raw, len, NULL); if (!ase) return JS_ThrowReferenceError(js, "failed to parse Aseprite file"); // Create array for frames JSValue frames_array = JS_NewArray(js); // Convert each frame to JS object for (int i = 0; i < ase->frame_count; i++) { JSValue frame_obj = frame_to_js(js, &ase->frames[i], ase->w, ase->h); JS_SetPropertyUint32(js, frames_array, i, frame_obj); } // Create result object JSValue result = JS_NewObject(js); JS_SetPropertyStr(js, result, "width", JS_NewInt32(js, ase->w)); JS_SetPropertyStr(js, result, "height", JS_NewInt32(js, ase->h)); JS_SetPropertyStr(js, result, "frames", frames_array); JS_SetPropertyStr(js, result, "frame_count", JS_NewInt32(js, ase->frame_count)); // Add palette if present if (ase->palette.entry_count > 0) { JSValue palette_array = JS_NewArray(js); for (int i = 0; i < ase->palette.entry_count; i++) { JSValue color = JS_NewObject(js); JS_SetPropertyStr(js, color, "r", JS_NewUint32(js, ase->palette.entries[i].color.r)); JS_SetPropertyStr(js, color, "g", JS_NewUint32(js, ase->palette.entries[i].color.g)); JS_SetPropertyStr(js, color, "b", JS_NewUint32(js, ase->palette.entries[i].color.b)); JS_SetPropertyStr(js, color, "a", JS_NewUint32(js, ase->palette.entries[i].color.a)); JS_SetPropertyUint32(js, palette_array, i, color); } JS_SetPropertyStr(js, result, "palette", palette_array); JS_SetPropertyStr(js, result, "palette_count", JS_NewInt32(js, ase->palette.entry_count)); } cute_aseprite_free(ase); return result; } // Aseprite encoding JSValue js_aseprite_encode(JSContext *js, JSValue this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError(js, "encode_aseprite requires an array of frames"); if (!JS_IsArray(js, argv[0])) return JS_ThrowTypeError(js, "first argument must be an array of frame objects"); // Get frame array length int frame_count = JS_ArrayLength(js, argv[0]); if (frame_count < 1) return JS_ThrowRangeError(js, "at least one frame is required"); // Get optional parameters int width = 0, height = 0; int tile_width = 0, tile_height = 0; if (argc > 1) { JSValue width_val = JS_GetPropertyStr(js, argv[1], "width"); JSValue height_val = JS_GetPropertyStr(js, argv[1], "height"); if (!JS_IsNull(width_val)) JS_ToInt32(js, &width, width_val); if (!JS_IsNull(height_val)) JS_ToInt32(js, &height, height_val); JS_FreeValue(js, width_val); JS_FreeValue(js, height_val); JSValue tile_w_val = JS_GetPropertyStr(js, argv[1], "tile_width"); JSValue tile_h_val = JS_GetPropertyStr(js, argv[1], "tile_height"); if (!JS_IsNull(tile_w_val)) JS_ToInt32(js, &tile_width, tile_w_val); if (!JS_IsNull(tile_h_val)) JS_ToInt32(js, &tile_height, tile_h_val); JS_FreeValue(js, tile_w_val); JS_FreeValue(js, tile_h_val); } // Allocate frames array ase_frame_t *frames = malloc(sizeof(ase_frame_t) * frame_count); if (!frames) return JS_ThrowOutOfMemory(js); // Process each frame for (int i = 0; i < frame_count; i++) { JSValue frame_val = JS_GetPropertyUint32(js, argv[0], i); // Get frame dimensions JSValue frame_w_val = JS_GetPropertyStr(js, frame_val, "width"); JSValue frame_h_val = JS_GetPropertyStr(js, frame_val, "height"); int frame_w, frame_h; if (JS_ToInt32(js, &frame_w, frame_w_val) < 0 || JS_ToInt32(js, &frame_h, frame_h_val) < 0) { free(frames); JS_FreeValue(js, frame_w_val); JS_FreeValue(js, frame_h_val); JS_FreeValue(js, frame_val); return JS_ThrowTypeError(js, "frame %d has invalid dimensions", i); } JS_FreeValue(js, frame_w_val); JS_FreeValue(js, frame_h_val); // Set global dimensions if not specified if (width == 0) width = frame_w; if (height == 0) height = frame_h; // Get pixel data JSValue pixels_val = JS_GetPropertyStr(js, frame_val, "pixels"); size_t pixel_len; void *pixel_data = js_get_blob_data(js, &pixel_len, pixels_val); JS_FreeValue(js, pixels_val); JS_FreeValue(js, frame_val); if (pixel_data == NULL) { free(frames); return JS_EXCEPTION; } if (!pixel_data) { free(frames); return JS_ThrowTypeError(js, "frame %d has no pixel data", i); } // Validate pixel data size size_t required_size = frame_w * frame_h * 4; if (pixel_len < required_size) { free(frames); return JS_ThrowRangeError(js, "frame %d pixel data too small (need %zu bytes, got %zu)", i, required_size, pixel_len); } // Get frame duration (default 100ms) int duration = 100; JSValue duration_val = JS_GetPropertyStr(js, frame_val, "duration"); if (!JS_IsNull(duration_val)) { JS_ToInt32(js, &duration, duration_val); } JS_FreeValue(js, duration_val); // Copy pixel data and set frame info frames[i].pixels = malloc(required_size); if (!frames[i].pixels) { free(frames); return JS_ThrowOutOfMemory(js); } memcpy(frames[i].pixels, pixel_data, required_size); frames[i].duration_milliseconds = duration; } // Set up aseprite structure ase_t ase = {0}; ase.w = width; ase.h = height; ase.frames = frames; ase.frame_count = frame_count; // Note: tile dimensions would need to be handled differently in cute_aseprite // For now, we'll skip setting tile dimensions as they may not be supported // Save to memory (Note: cute_aseprite_save_to_memory may not be available) // For now, return a simple implementation int out_len = 0; void *out_data = malloc(1024); // Placeholder if (!out_data) { free(frames); return JS_ThrowOutOfMemory(js); } // Placeholder implementation - would need actual cute_aseprite save functionality memset(out_data, 0, 1024); out_len = 1024; // Clean up frame pixel data for (int i = 0; i < frame_count; i++) { free(frames[i].pixels); } free(frames); if (!out_data) return JS_ThrowInternalError(js, "failed to encode Aseprite file"); // 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, "frame_count", JS_NewInt32(js, frame_count)); JS_SetPropertyStr(js, result, "format", JS_NewString(js, "aseprite")); JSValue compressed_pixels = js_new_blob_stoned_copy(js, out_data, out_len); free(out_data); JS_SetPropertyStr(js, result, "pixels", compressed_pixels); return result; } static const JSCFunctionListEntry js_aseprite_funcs[] = { MIST_FUNC_DEF(aseprite, encode, 1), MIST_FUNC_DEF(aseprite, decode, 1) }; CELL_USE_FUNCS(js_aseprite_funcs)