add aseprite support

This commit is contained in:
John Alanbrook 2024-10-18 12:51:21 -05:00
parent 12bb5e084a
commit 27d7c5ab02
7 changed files with 149 additions and 67 deletions

View file

@ -88,10 +88,10 @@ game.engine_start = function (s) {
1, 1, 0], 0), 1, 1, 0], 0),
verts: 4, verts: 4,
uv: os.make_buffer([ uv: os.make_buffer([
0, 0,
0, 1, 0, 1,
1, 0, 0, 0,
1, 1], 2), 1, 1,
1, 0], 2),
index: os.make_buffer([0, 1, 2, 2, 1, 3], 1), index: os.make_buffer([0, 1, 2, 2, 1, 3], 1),
count: 6, count: 6,
}; };
@ -112,10 +112,10 @@ game.engine_start = function (s) {
0.5, 0.5, -0.5], 0), 0.5, 0.5, -0.5], 0),
verts: 4, verts: 4,
uv: os.make_buffer([ uv: os.make_buffer([
0, 0,
0, 1, 0, 1,
1, 0, 0, 0,
1, 1], 2), 1, 1,
1, 0], 2),
index: os.make_buffer([0, 1, 2, 2, 1, 3], 1), index: os.make_buffer([0, 1, 2, 2, 1, 3], 1),
count: 6, count: 6,
}; };
@ -255,7 +255,9 @@ function pack_into_sheet(images)
game.texture = function (path) { game.texture = function (path) {
if (!path) return game.texture("icons/no_tex.gif"); if (!path) return game.texture("icons/no_tex.gif");
path = Resources.find_image(path);
var parts = path.split(':');
path = Resources.find_image(parts[0]);
if (!io.exists(path)) { if (!io.exists(path)) {
console.error(`Missing texture: ${path}`); console.error(`Missing texture: ${path}`);
@ -263,12 +265,38 @@ game.texture = function (path) {
game.texture.time_cache[path] = io.mod(path); game.texture.time_cache[path] = io.mod(path);
return game.texture.cache[path]; return game.texture.cache[path];
} }
if (game.texture.cache[path]) return game.texture.cache[path];
var tex = os.make_texture(path); var cachestr = path;
if (parts[1])
cachestr += parts[1];
var frame;
var anim_str;
if (parts.length > 1) {
// it's an animation
path = parts[0];
parts = parts[1].split('_'); // For a gif, it might be 'water.gif:3', but for an ase it might be 'water.ase:run_3', meaning the third frame of the 'run' animation
if (parts.length === 1)
frame = Number(parts[0]);
else {
anim_str = parts[0];
frame = Number(parts[1]);
}
} else
parts = undefined;
if (game.texture.cache[cachestr]) return game.texture.cache[path];
var tex = os.make_texture(path.split(':')[0]);
if (!tex) return;
var image; var image;
var anim = SpriteAnim.make(path, tex); var ext = path.ext();
var anim;
if (ext === 'ase' || ext === 'aseprite') {
anim = os.make_aseprite(path);
}
if (!anim) { if (!anim) {
image = { image = {
texture: tex, texture: tex,
@ -293,12 +321,12 @@ game.texture = function (path) {
tex = spritesheet; tex = spritesheet;
} }
game.texture.cache[path] = image; game.texture.cache[cachestr] = image;
game.texture.time_cache[path] = io.mod(path); game.texture.time_cache[path] = io.mod(path);
tex.load_gpu(); tex.load_gpu();
return game.texture.cache[path]; return game.texture.cache[cachestr];
}; };
game.texture.cache = {}; game.texture.cache = {};
game.texture.time_cache = {}; game.texture.time_cache = {};

View file

@ -1012,7 +1012,9 @@ render.image = function image(image, rect = [0,0], rotation = 0, color = Color.w
var tex = image.texture; var tex = image.texture;
if (!tex) return; if (!tex) return;
var size = [rect.width ? rect.width : tex.width, rect.height ? rect.height : tex.height]; var image_size = [image.rect.width*tex.width, image.rect.height*tex.height];
var size = [rect.width ? rect.width : image_size.x, rect.height ? rect.height : image_size.y];
if (!lasttex) { if (!lasttex) {
check_flush(flush_img); check_flush(flush_img);
@ -1026,7 +1028,7 @@ render.image = function image(image, rect = [0,0], rotation = 0, color = Color.w
var e = img_e(); var e = img_e();
var pos = [rect.x,rect.y].sub(size.scale([rect.anchor_x, rect.anchor_y])); var pos = [rect.x,rect.y].sub(size.scale([rect.anchor_x, rect.anchor_y]));
e.transform.trs(pos, undefined, size.div([tex.width,tex.height])); e.transform.trs(pos, undefined, size.div(image_size));
e.image = image; e.image = image;
e.shade = color; e.shade = color;

View file

@ -7,8 +7,12 @@
#include "sokol/sokol_args.h" #include "sokol/sokol_args.h"
#include "sokol/sokol_gfx.h" #include "sokol/sokol_gfx.h"
#include "sokol/sokol_app.h" #include "sokol/sokol_app.h"
#include "sokol/sokol_fetch.h"
#include "sokol/util/sokol_gl.h" #include "sokol/util/sokol_gl.h"
#define CUTE_ASEPRITE_IMPLEMENTATION
#include "cute_aseprite.h"
#define LAY_FLOAT 1 #define LAY_FLOAT 1
#define LAY_IMPLEMENTATION #define LAY_IMPLEMENTATION
#include "layout.h" #include "layout.h"

View file

@ -11,6 +11,7 @@
#include "datastream.h" #include "datastream.h"
#include "sound.h" #include "sound.h"
#include "stb_ds.h" #include "stb_ds.h"
#include "stb_image.h"
#include "stb_rect_pack.h" #include "stb_rect_pack.h"
#include "string.h" #include "string.h"
#include "window.h" #include "window.h"
@ -40,6 +41,8 @@
#include "gui.h" #include "gui.h"
#include "timer.h" #include "timer.h"
#include "cute_aseprite.h"
#define LAY_FLOAT 1 #define LAY_FLOAT 1
#include "layout.h" #include "layout.h"
@ -627,8 +630,8 @@ JSValue rect2js(struct rect rect) {
JSValue obj = JS_NewObject(js); JSValue obj = JS_NewObject(js);
js_setprop_str(obj, "x", number2js(rect.x)); js_setprop_str(obj, "x", number2js(rect.x));
js_setprop_str(obj, "y", number2js(rect.y)); js_setprop_str(obj, "y", number2js(rect.y));
js_setprop_str(obj, "w", number2js(rect.w)); js_setprop_str(obj, "width", number2js(rect.w));
js_setprop_str(obj, "h", number2js(rect.h)); js_setprop_str(obj, "height", number2js(rect.h));
return obj; return obj;
} }
@ -2758,8 +2761,6 @@ static const JSCFunctionListEntry js_datastream_funcs[] = {
JSC_GET(texture, width, number) JSC_GET(texture, width, number)
JSC_GET(texture, height, number) JSC_GET(texture, height, number)
JSC_GET(texture, frames, number)
JSC_GET(texture, delays, ints)
JSC_GET(texture, vram, number) JSC_GET(texture, vram, number)
JSC_SCALL(texture_save, texture_save(js2texture(self), str)); JSC_SCALL(texture_save, texture_save(js2texture(self), str));
@ -2811,8 +2812,6 @@ JSC_CCALL(texture_offload,
static const JSCFunctionListEntry js_texture_funcs[] = { static const JSCFunctionListEntry js_texture_funcs[] = {
MIST_GET(texture, width), MIST_GET(texture, width),
MIST_GET(texture, height), MIST_GET(texture, height),
MIST_GET(texture, frames),
MIST_GET(texture, delays),
MIST_GET(texture, vram), MIST_GET(texture, vram),
MIST_FUNC_DEF(texture, save, 1), MIST_FUNC_DEF(texture, save, 1),
MIST_FUNC_DEF(texture, write_pixel, 2), MIST_FUNC_DEF(texture, write_pixel, 2),
@ -3377,12 +3376,101 @@ JSC_SCALL(os_make_texture,
JS_SetPropertyStr(js, ret, "path", JS_DupValue(js,argv[0])); JS_SetPropertyStr(js, ret, "path", JS_DupValue(js,argv[0]));
) )
JSC_SCALL(os_make_pcm, JSC_SCALL(os_make_gif,
ret = pcm2js(make_pcm(str)); size_t rawlen;
unsigned char *raw = slurp_file(str, &rawlen);
if (!raw) goto ENDEND;
int n;
texture *tex = calloc(1,sizeof(*tex));
int frames;
int *delays;
tex->data = stbi_load_gif_from_memory(raw, rawlen, &delays, &tex->width, &tex->height, &frames, &n, 4);
JSValue gif = JS_NewObject(js);
js_setpropstr(gif, "texture", texture2js(tex));
JSValue delay_arr = JS_NewArray(js);
float yslice = 1.0/frames;
for (int i = 0; i < frames; i++) {
JSValue frame = JS_NewObject(js);
js_setpropstr(frame, "time", number2js((float)delays[i]/1000.0));
js_setpropstr(frame, "rect", rect2js((rect){
.x = 0,
.y = yslice*i,
.w = 1,
.h = yslice
}));
js_setprop_num(delay_arr, i, frame);
}
js_setpropstr(gif, "delays", delay_arr);
free(delays);
ret = gif;
END:
free(raw);
ENDEND:
) )
JSC_SCALL(os_make_aseprite, JSC_SCALL(os_make_aseprite,
size_t rawlen;
unsigned char *raw = slurp_file(str, &rawlen);
ase_t *ase = cute_aseprite_load_from_memory(raw, rawlen, NULL);
JSValue obj = JS_NewObject(js);
int w = ase->w;
int h = ase->h;
int pixels = w*h;
for (int t = 0; t < ase->tag_count; t++) {
ase_tag_t tag = ase->tags[t];
JSValue anim = JS_NewObject(js);
js_setpropstr(anim, "repeat", number2js(tag.repeat));
switch(tag.loop_animation_direction) {
case ASE_ANIMATION_DIRECTION_FORWARDS:
js_setpropstr(anim, "loop", str2js("forward"));
break;
case ASE_ANIMATION_DIRECTION_BACKWORDS:
js_setpropstr(anim, "loop", str2js("backward"));
break;
case ASE_ANIMATION_DIRECTION_PINGPONG:
js_setpropstr(anim, "loop", str2js("pingpong"));
break;
}
int _frame = 0;
JSValue frames = JS_NewArray(js);
for (int f = tag.from_frame; f <= tag.to_frame; f++) {
ase_frame_t aframe = ase->frames[f];
JSValue frame = JS_NewObject(js);
texture *tex = calloc(1,sizeof(*tex));
tex->width = w;
tex->height = h;
tex->data = malloc(w*h*4);
memcpy(tex->data, aframe.pixels, w*h*4);
js_setpropstr(frame, "texture", texture2js(tex));
js_setpropstr(frame, "rect", rect2js((rect){.x=0,.y=0,.w=1,.h=1}));
js_setpropstr(frame, "time", number2js((float)aframe.duration_milliseconds/1000.0));
js_setprop_num(frames, _frame, frame);
_frame++;
}
js_setpropstr(anim, "frames", frames);
js_setpropstr(obj, tag.name, anim);
}
ret = obj;
cute_aseprite_free(ase);
)
JSC_SCALL(os_make_pcm,
ret = pcm2js(make_pcm(str));
) )
JSC_SCALL(os_texture_swap, JSC_SCALL(os_texture_swap,
@ -3678,6 +3766,8 @@ static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, make_poly2d, 2), MIST_FUNC_DEF(os, make_poly2d, 2),
MIST_FUNC_DEF(os, make_seg2d, 1), MIST_FUNC_DEF(os, make_seg2d, 1),
MIST_FUNC_DEF(os, make_texture, 1), MIST_FUNC_DEF(os, make_texture, 1),
MIST_FUNC_DEF(os, make_gif, 1),
MIST_FUNC_DEF(os, make_aseprite, 1),
MIST_FUNC_DEF(os, make_pcm, 1), MIST_FUNC_DEF(os, make_pcm, 1),
MIST_FUNC_DEF(os, texture_swap, 2), MIST_FUNC_DEF(os, texture_swap, 2),
MIST_FUNC_DEF(os, make_tex_data, 3), MIST_FUNC_DEF(os, make_tex_data, 3),

View file

@ -21,9 +21,7 @@
#include <ftw.h> #include <ftw.h>
#endif #endif
#define SOKOL_FETCH_IMPL
#include "sokol/sokol_fetch.h" #include "sokol/sokol_fetch.h"
#include "stb_ds.h" #include "stb_ds.h"
#include "core.cdb.h" #include "core.cdb.h"

View file

@ -15,9 +15,6 @@
#include "qoi.h" #include "qoi.h"
#define CUTE_ASEPRITE_IMPLEMENTATION
#include "cute_aseprite.h"
#ifndef NSVG #ifndef NSVG
#include "nanosvgrast.h" #include "nanosvgrast.h"
#endif #endif
@ -175,50 +172,16 @@ struct texture *texture_from_file(const char *path) {
struct texture *tex = calloc(1, sizeof(*tex)); struct texture *tex = calloc(1, sizeof(*tex));
stbi_set_flip_vertically_on_load(1);
int n; int n;
char *ext = strrchr(path, '.'); char *ext = strrchr(path, '.');
if (!strcmp(ext, ".ase")) { if (!strcmp(ext, ".qoi")) {
ase_t *ase = cute_aseprite_load_from_memory(raw, rawlen, NULL);
// frame is ase->w, ase->h
/* sprite anim is
anim = {
frames: [
rect: { <---- gathered after rect packing
x:
y:
w:
h:
},
time: ase->duration_milliseconds / 1000 (so it's in seconds)
],
path: path,
};
*/
for (int i = 0; i < ase->frame_count; i++) {
ase_frame_t *frame = ase->frames+i;
// add to thing with frame->pixels
}
cute_aseprite_free(ase);
} else if (!strcmp(ext, ".qoi")) {
qoi_desc qoi; qoi_desc qoi;
data = qoi_decode(raw, rawlen, &qoi, 4); data = qoi_decode(raw, rawlen, &qoi, 4);
tex->width = qoi.width; tex->width = qoi.width;
tex->height = qoi.height; tex->height = qoi.height;
n = qoi.channels; n = qoi.channels;
} else if (!strcmp(ext, ".gif")) {
data = stbi_load_gif_from_memory(raw, rawlen, &tex->delays, &tex->width, &tex->height, &tex->frames, &n, 4);
int *dd = tex->delays;
tex->delays = NULL;
arrsetlen(tex->delays, tex->frames);
for (int i = 0; i < tex->frames; i++) tex->delays[i] = dd[i];
free(dd);
tex->height *= tex->frames;
} else if (!strcmp(ext, ".svg")) { } else if (!strcmp(ext, ".svg")) {
#ifndef NSVG #ifndef NSVG
NSVGimage *svg = nsvgParse(raw, "px", 96); NSVGimage *svg = nsvgParse(raw, "px", 96);
@ -256,7 +219,6 @@ void texture_free(texture *tex)
if (!tex) return; if (!tex) return;
if (tex->data) if (tex->data)
free(tex->data); free(tex->data);
if (tex->delays) arrfree(tex->delays);
if (tex->simgui.id) if (tex->simgui.id)
simgui_destroy_image(tex->simgui); simgui_destroy_image(tex->simgui);

View file

@ -29,8 +29,6 @@ struct texture {
int height; int height;
HMM_Vec3 dimensions; HMM_Vec3 dimensions;
unsigned char *data; unsigned char *data;
int frames;
int *delays;
int vram; int vram;
}; };