Files
prosperon/graphics.c
2025-11-26 12:52:56 -06:00

287 lines
8.9 KiB
C

#include "cell.h"
#include "jsffi.h"
#include "HandmadeMath.h"
#include "datastream.h"
#define STB_IMAGE_IMPLEMENTATION
#define STBI_FAILURE_USERMSG
#define STBI_NO_STDIO
#include "stb_image.h"
#define STB_RECT_PACK_IMPLEMENTATION
#include "stb_rect_pack.h"
#define CUTE_ASEPRITE_IMPLEMENTATION
#include "cute_aseprite.h"
// input: (encoded image data of jpg, png, bmp, tiff)
JSC_CCALL(os_image_decode,
size_t len;
void *raw = js_get_blob_data(js, &len, argv[0]);
if (!raw) return JS_ThrowReferenceError(js, "could not load texture with array buffer");
int n, width, height;
void *data = stbi_load_from_memory(raw, len, &width, &height, &n, 4);
if (!data)
return JS_ThrowReferenceError(js, "no known image type from pixel data: %s", stbi_failure_reason());
if (width <= 0 || height <= 0) {
free(data);
return JS_ThrowReferenceError(js, "decoded image has invalid size: %dx%d", width, height);
}
int pitch = width*4;
size_t pixels_size = pitch * height;
// Create JS object with surface 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, 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));
free(data);
ret = obj;
)
JSC_CCALL(graphics_hsl_to_rgb,
float h, s, l;
JS_ToFloat64(js, &h, argv[0]);
JS_ToFloat64(js, &s, argv[1]);
JS_ToFloat64(js, &l, argv[2]);
float c = (1 - abs(2 * l - 1)) * s;
float x = c * (1 - abs(fmod((h/60),2) - 1));
float m = l - c / 2;
float r = 0, g = 0, b = 0;
if (h < 60) { r = c; g = x; }
else if (h < 120) { r = x; g = c; }
else if (h < 180) { g = c; b = x; }
else if (h < 240) { g = x; b = c; }
else if (h < 300) { r = x; b = c; }
else { r = c; b = x; }
return color2js(js, (colorf){r+m, g+m, b+m, 1});
)
JSC_CCALL(os_make_line_prim,
return JS_NULL;
/*
JSValue prim = JS_NewObject(js);
HMM_Vec2 *v = js2cpvec2arr(js,argv[0]);
parsl_context *par_ctx = parsl_create_context((parsl_config){
.thickness = js2number(js,argv[1]),
.flags= PARSL_FLAG_ANNOTATIONS,
.u_mode = js2number(js,argv[2])
});
uint16_t spine_lens[] = {arrlen(v)};
parsl_mesh *m = parsl_mesh_from_lines(par_ctx, (parsl_spine_list){
.num_vertices = arrlen(v),
.num_spines = 1,
.vertices = v,
.spine_lengths = spine_lens,
.closed = JS_ToBool(js,argv[3])
});
JS_SetPropertyStr(js, prim, "pos", make_gpu_buffer(js,m->positions,sizeof(*m->positions)*m->num_vertices, 0, 2,1,0));
JS_SetPropertyStr(js, prim, "indices", make_gpu_buffer(js,m->triangle_indices,sizeof(*m->triangle_indices)*m->num_triangles*3, JS_TYPED_ARRAY_UINT32, 1,1,1));
float uv[m->num_vertices*2];
for (int i = 0; i < m->num_vertices; i++) {
uv[i*2] = m->annotations[i].u_along_curve;
uv[i*2+1] = m->annotations[i].v_across_curve;
}
JS_SetPropertyStr(js, prim, "uv", make_gpu_buffer(js, uv, sizeof(uv), 0,2,1,0));
JS_SetPropertyStr(js,prim,"vertices", number2js(js,m->num_vertices));
JS_SetPropertyStr(js,prim,"num_indices", number2js(js,m->num_triangles*3));
JS_SetPropertyStr(js,prim,"first_index", number2js(js,0));
parsl_destroy_context(par_ctx);
return prim;
*/
)
JSC_CCALL(os_rectpack,
int width = js2number(js,argv[0]);
int height = js2number(js,argv[1]);
int num = JS_ArrayLength(js,argv[2]);
stbrp_context ctx[1];
stbrp_rect rects[num];
for (int i = 0; i < num; i++) {
HMM_Vec2 wh = js2vec2(js,JS_GetPropertyUint32(js, argv[2], i));
rects[i].w = wh.x;
rects[i].h = wh.y;
rects[i].id = i;
}
stbrp_node nodes[width];
stbrp_init_target(ctx, width, height, nodes, width);
int packed = stbrp_pack_rects(ctx, rects, num);
if (!packed) {
return JS_NULL;
}
ret = JS_NewArray(js);
for (int i = 0; i < num; i++) {
HMM_Vec2 pos;
pos.x = rects[i].x;
pos.y = rects[i].y;
JS_SetPropertyUint32(js, ret, i, vec22js(js,pos));
}
)
// input: (gif image data)
JSC_CCALL(os_make_gif,
size_t rawlen;
void *raw = js_get_blob_data(js, &rawlen, argv[0]);
if (!raw) return JS_ThrowReferenceError(js, "could not load gif from supplied array buffer");
int n;
int frames;
int *delays;
int width;
int height;
void *pixels = stbi_load_gif_from_memory(raw, rawlen, &delays, &width, &height, &frames, &n, 4);
if (!pixels) {
return JS_ThrowReferenceError(js, "1decode GIF: %s", stbi_failure_reason());
}
// Always return an array of surfaces, even for single frame
JSValue surface_array = JS_NewArray(js);
ret = surface_array;
for (int i = 0; i < frames; i++) {
// Create surface data object
JSValue surfData = JS_NewObject(js);
JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, width));
JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, height));
JS_SetPropertyStr(js, surfData, "format", JS_NewString(js, "rgba32"));
JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, width*4));
void *frame_pixels = (unsigned char*)pixels+(width*height*4*i);
JS_SetPropertyStr(js, surfData, "pixels", js_new_blob_stoned_copy(js, frame_pixels, width*height*4));
// Add time property for animation frames
if (frames > 1 && delays) {
JS_SetPropertyStr(js, surfData, "time", number2js(js,(float)delays[i]/1000.0));
}
JS_SetPropertyUint32(js, surface_array, i, surfData);
}
CLEANUP:
if (delays) free(delays);
if (pixels) free(pixels);
)
JSValue aseframe2js(JSContext *js, ase_frame_t aframe)
{
JSValue frame = JS_NewObject(js);
// Create surface data object instead of SDL_Surface
JSValue surfData = JS_NewObject(js);
JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, aframe.ase->w));
JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, aframe.ase->h));
JS_SetPropertyStr(js, surfData, "format", JS_NewString(js, "rgba32"));
JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, aframe.ase->w*4));
JS_SetPropertyStr(js, surfData, "pixels", js_new_blob_stoned_copy(js, aframe.pixels, aframe.ase->w*aframe.ase->h*4));
JS_SetPropertyStr(js, frame, "surface", surfData);
JS_SetPropertyStr(js, frame, "time", number2js(js,(float)aframe.duration_milliseconds/1000.0));
return frame;
}
// input: (aseprite data)
JSC_CCALL(os_make_aseprite,
size_t rawlen;
void *raw = js_get_blob_data(js,&rawlen,argv[0]);
ase_t *ase = cute_aseprite_load_from_memory(raw, rawlen, NULL);
if (!ase)
return JS_ThrowReferenceError(js, "could not load aseprite from supplied array buffer: %s", aseprite_GetError());
if (ase->tag_count == 0) {
// we're dealing with a single frame image, or single animation
if (ase->frame_count == 1) {
JSValue obj = aseframe2js(js,ase->frames[0]);
cute_aseprite_free(ase);
return obj;
} else {
// Multiple frames but no tags - create a simple animation
JSValue obj = JS_NewObject(js);
JSValue frames = JS_NewArray(js);
for (int f = 0; f < ase->frame_count; f++) {
JSValue frame = aseframe2js(js,ase->frames[f]);
JS_SetPropertyUint32(js, frames, f, frame);
}
JS_SetPropertyStr(js, obj, "frames", frames);
JS_SetPropertyStr(js, obj, "loop", JS_NewBool(js, true));
ret = obj;
cute_aseprite_free(ase);
return ret;
}
}
JSValue obj = JS_NewObject(js);
for (int t = 0; t < ase->tag_count; t++) {
ase_tag_t tag = ase->tags[t];
JSValue anim = JS_NewObject(js);
JS_SetPropertyStr(js, anim, "repeat", number2js(js,tag.repeat));
switch(tag.loop_animation_direction) {
case ASE_ANIMATION_DIRECTION_FORWARDS:
JS_SetPropertyStr(js, anim, "loop", JS_NewString(js,"forward"));
break;
case ASE_ANIMATION_DIRECTION_BACKWORDS:
JS_SetPropertyStr(js, anim, "loop", JS_NewString(js,"backward"));
break;
case ASE_ANIMATION_DIRECTION_PINGPONG:
JS_SetPropertyStr(js, anim, "loop", JS_NewString(js,"pingpong"));
break;
}
int _frame = 0;
JSValue frames = JS_NewArray(js);
for (int f = tag.from_frame; f <= tag.to_frame; f++) {
JSValue frame = aseframe2js(js,ase->frames[f]);
JS_SetPropertyUint32(js, frames, _frame, frame);
_frame++;
}
JS_SetPropertyStr(js, anim, "frames", frames);
JS_SetPropertyStr(js, obj, tag.name, anim);
}
ret = obj;
cute_aseprite_free(ase);
)
const JSCFunctionListEntry js_graphics_funcs[] = {
MIST_FUNC_DEF(os, rectpack, 3),
MIST_FUNC_DEF(os, image_decode, 1),
MIST_FUNC_DEF(os, make_gif, 1),
MIST_FUNC_DEF(os, make_aseprite, 1),
MIST_FUNC_DEF(os, make_line_prim, 5),
MIST_FUNC_DEF(graphics, hsl_to_rgb, 3),
};
JSValue js_graphics_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_graphics_funcs,countof(js_graphics_funcs));
return mod;
}