287 lines
8.9 KiB
C
287 lines
8.9 KiB
C
#include "cell.h"
|
|
#include "prosperon.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;
|
|
} |