Files
cell-image/aseprite.c
2026-01-13 22:17:38 -06:00

242 lines
8.4 KiB
C

#include "cell.h"
#include <string.h>
#include <stdlib.h>
#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)