add animation test; help qr API
Some checks failed
Build and Deploy / build-macos (push) Failing after 5s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
Some checks failed
Build and Deploy / build-macos (push) Failing after 5s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
This commit is contained in:
@@ -11,10 +11,11 @@ A collection of 2D drawing functions that operate in screen space. Provides prim
|
||||
for lines, rectangles, text, sprite drawing, etc. Immediate mode.
|
||||
`
|
||||
|
||||
var whiteimage = {}
|
||||
whiteimage.surface = graphics.make_surface([1,1])
|
||||
whiteimage.surface.rect({x:0,y:0,width:1,height:1}, [1,1,1,1])
|
||||
/*var whiteimage = {}
|
||||
whiteimage = graphics.make_surface([1,1])
|
||||
whiteimage.rect({x:0,y:0,width:1,height:1}, [1,1,1,1])
|
||||
render.load_texture(whiteimage)
|
||||
*/
|
||||
|
||||
if (render.point)
|
||||
draw.point = function(pos,size,opt = {color:Color.white}, pipeline) {
|
||||
|
||||
@@ -10,6 +10,51 @@ var io = use('io')
|
||||
var res = use('resources')
|
||||
var render = use('render')
|
||||
|
||||
var GPU = Symbol()
|
||||
var CPU = Symbol()
|
||||
var LASTUSE = Symbol()
|
||||
|
||||
var cache = new Map()
|
||||
|
||||
// When creating an image, do an Object.create(graphics.Image)
|
||||
graphics.Image = {
|
||||
get gpu() {
|
||||
this[LASTUSE] = os.now();
|
||||
if (!this[GPU]) {
|
||||
this[GPU] = render.load_texture(this[CPU]);
|
||||
decorate_rect_px(this);
|
||||
}
|
||||
|
||||
return this[GPU]
|
||||
},
|
||||
|
||||
get texture() { return this.gpu },
|
||||
|
||||
get cpu() {
|
||||
this[LASTUSE] = os.now();
|
||||
if (!this[CPU]) this[CPU] = render.read_texture(this[GPU])
|
||||
return this[CPU]
|
||||
},
|
||||
|
||||
get surface() { return this.cpu },
|
||||
|
||||
get width() {
|
||||
return this[GPU].width
|
||||
},
|
||||
|
||||
get height() {
|
||||
return this[GPU].height
|
||||
},
|
||||
|
||||
unload_gpu() {
|
||||
this[GPU] = undefined
|
||||
},
|
||||
|
||||
unload_cpu() {
|
||||
this[CPU] = undefined
|
||||
},
|
||||
}
|
||||
|
||||
function calc_image_size(img) {
|
||||
if (!img.texture || !img.rect) return
|
||||
return [img.texture.width * img.rect.width, img.texture.height * img.rect.height]
|
||||
@@ -31,71 +76,82 @@ function decorate_rect_px(img) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Internally loads image data from disk and prepares a GPU texture. Used by graphics.texture().
|
||||
Not intended for direct user calls.
|
||||
*/
|
||||
function create_image(path) {
|
||||
try {
|
||||
var data = io.slurpbytes(path);
|
||||
var newimg;
|
||||
|
||||
switch (path.ext()) {
|
||||
case 'gif':
|
||||
newimg = graphics.make_gif(data);
|
||||
if (newimg.surface) {
|
||||
render.load_texture(newimg)
|
||||
decorate_rect_px(newimg)
|
||||
}
|
||||
else {
|
||||
for (var frame of newimg.frames) {
|
||||
render.load_texture(frame)
|
||||
decorate_rect_px(frame)
|
||||
}
|
||||
}
|
||||
break;
|
||||
function make_handle(obj)
|
||||
{
|
||||
var image = Object.create(graphics.Image);
|
||||
|
||||
if (obj.surface) {
|
||||
im
|
||||
}
|
||||
return Object.assign(Object.create(graphics.Image), {
|
||||
rect:{x:0,y:0,width:1,height:1},
|
||||
[CPU]:obj,
|
||||
[GPU]:undefined,
|
||||
[LASTUSE]:os.now()
|
||||
})
|
||||
}
|
||||
|
||||
case 'ase':
|
||||
case 'aseprite':
|
||||
newimg = graphics.make_aseprite(data);
|
||||
if (newimg.surface) {
|
||||
render.load_texture(newimg)
|
||||
decorate_rect_px(newimg)
|
||||
}
|
||||
else {
|
||||
for (var anim in newimg) {
|
||||
var a = newimg[anim];
|
||||
for (var frame of a.frames) {
|
||||
render.load_texture(frame)
|
||||
decorate_rect_px(frame)
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
function wrapSurface(surf, maybeRect){
|
||||
const h = make_handle(surf);
|
||||
if(maybeRect) h.rect = maybeRect; /* honour frame sub-rect */
|
||||
return h;
|
||||
}
|
||||
function wrapFrames(arr){ /* [{surface,time,rect}, …] → [{image,time}] */
|
||||
return arr.map(f => ({
|
||||
image : wrapSurface(f.surface || f), /* accept bare surface too */
|
||||
time: f.time,
|
||||
rect: f.rect /* keep for reference */
|
||||
}));
|
||||
}
|
||||
function makeAnim(frames, loop=true){
|
||||
return { frames, loop }
|
||||
}
|
||||
|
||||
default:
|
||||
newimg = {
|
||||
surface: graphics.make_texture(data)
|
||||
};
|
||||
render.load_texture(newimg)
|
||||
decorate_rect_px(newimg)
|
||||
break;
|
||||
}
|
||||
|
||||
return newimg;
|
||||
|
||||
} catch (e) {
|
||||
// Add the path to the error message for better debugging
|
||||
console.error(`Error loading image from path: ${path}`);
|
||||
console.error(e.message);
|
||||
if (e.stack) {
|
||||
console.error(e.stack);
|
||||
}
|
||||
// Optionally, you can throw the error again to let it propagate
|
||||
throw e;
|
||||
function decode_image(bytes, ext)
|
||||
{
|
||||
switch(ext) {
|
||||
case 'gif': return graphics.make_gif(bytes)
|
||||
case 'ase':
|
||||
case 'aseprite': return graphics.make_aseprite(bytes)
|
||||
default: return {surface:graphics.make_texture(bytes)}
|
||||
}
|
||||
}
|
||||
|
||||
function create_image(path){
|
||||
try{
|
||||
const bytes = io.slurpbytes(path);
|
||||
let raw = decode_image(bytes, path.ext());
|
||||
|
||||
/* ── Case A: static image ─────────────────────────────────── */
|
||||
if(raw.surface)
|
||||
return make_handle(raw.surface);
|
||||
|
||||
/* ── Case B: GIF helpers returned array [surf, …] ─────────── */
|
||||
if(Array.isArray(raw))
|
||||
return makeAnim( wrapFrames(raw), true );
|
||||
|
||||
/* ── Case C: GIF helpers returned {frames,loop} ───────────── */
|
||||
if(raw.frames && Array.isArray(raw.frames))
|
||||
return makeAnim( wrapFrames(raw.frames), !!raw.loop );
|
||||
|
||||
/* ── Case D: ASE helpers returned { animName:{frames,loop}, … } ── */
|
||||
const anims = {};
|
||||
for(const [name, anim] of Object.entries(raw)){
|
||||
if(anim && Array.isArray(anim.frames))
|
||||
anims[name] = makeAnim( wrapFrames(anim.frames), !!anim.loop );
|
||||
else if(anim && anim.surface) /* ase with flat surface */
|
||||
anims[name] = makeAnim(
|
||||
[{image:make_handle(anim.surface),time:0}], true );
|
||||
}
|
||||
if(Object.keys(anims).length) return anims;
|
||||
|
||||
throw new Error('Unsupported image structure from decoder');
|
||||
|
||||
}catch(e){
|
||||
console.error(`Error loading image ${path}: ${e.message}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
var image = {}
|
||||
image.dimensions = function() {
|
||||
@@ -140,15 +196,36 @@ graphics.texture_from_data = function(data)
|
||||
return img
|
||||
}
|
||||
|
||||
graphics.from_surface = function(id, surf)
|
||||
{
|
||||
return make_handle(surf)
|
||||
var img = { surface: surf }
|
||||
}
|
||||
|
||||
graphics.from = function(id, data)
|
||||
{
|
||||
if (typeof id !== 'string')
|
||||
throw new Error('Expected a string ID')
|
||||
|
||||
if (data instanceof ArrayBuffer)
|
||||
return graphics.texture_from_data(data)
|
||||
}
|
||||
|
||||
graphics.texture = function texture(path) {
|
||||
if (typeof path !== 'string') {
|
||||
return path // fallback if already an image object
|
||||
if (path.__proto__ === graphics.Image) return path
|
||||
|
||||
if (typeof path !== 'string')
|
||||
throw new Error('need a string for graphics.texture')
|
||||
}
|
||||
var parts = path.split(':')
|
||||
var ipath = res.find_image(parts[0])
|
||||
graphics.texture.cache[ipath] ??= create_image(ipath)
|
||||
return graphics.texture.cache[ipath]
|
||||
|
||||
var id = path.split(':')[0]
|
||||
if (cache.has(id)) return cache.get(id)
|
||||
|
||||
var ipath = res.find_image(id)
|
||||
if (!ipath) throw new Error(`unknown image ${id}`)
|
||||
|
||||
var image = create_image(ipath)
|
||||
cache.set(id, image)
|
||||
return image
|
||||
}
|
||||
graphics.texture[prosperon.DOC] = `
|
||||
:param path: A string path to an image file or an already-loaded image object.
|
||||
@@ -228,9 +305,12 @@ graphics.get_font = function get_font(path, size) {
|
||||
if (fontcache[fontstr]) return fontcache[fontstr]
|
||||
|
||||
var data = io.slurpbytes(fullpath)
|
||||
fontcache[fontstr] = graphics.make_font(data, size)
|
||||
render.load_texture(fontcache[fontstr])
|
||||
return fontcache[fontstr]
|
||||
var font = graphics.make_font(data,size)
|
||||
font.texture = render.load_texture(font.surface)
|
||||
|
||||
fontcache[fontstr] = font
|
||||
|
||||
return font
|
||||
}
|
||||
graphics.get_font[prosperon.DOC] = `
|
||||
:param path: A string path to a font file, optionally with ".size" appended.
|
||||
|
||||
@@ -38,15 +38,9 @@ render.sprite = function(sprite)
|
||||
}
|
||||
|
||||
// img here is the engine surface
|
||||
render.load_texture = function(img)
|
||||
render.load_texture = function(surface)
|
||||
{
|
||||
if (!img.surface)
|
||||
throw new Error('Image must have a surface.')
|
||||
|
||||
if (img.texture)
|
||||
throw new Error('Image has already been uploaded to GPU.')
|
||||
|
||||
img.texture = context.load_texture(img.surface)
|
||||
return context.load_texture(surface)
|
||||
}
|
||||
|
||||
var current_color = Color.white
|
||||
|
||||
140
source/jsffi.c
140
source/jsffi.c
@@ -1391,6 +1391,24 @@ rect js2rect(JSContext *js,JSValue v) {
|
||||
return rect;
|
||||
}
|
||||
|
||||
irect js2irect(JSContext *js, JSValue v)
|
||||
{
|
||||
if (JS_IsUndefined(v)) return (irect){0,0,1,1};
|
||||
irect rect;
|
||||
JS_GETATOM(js,rect.x,v,x,number)
|
||||
JS_GETATOM(js,rect.y,v,y,number)
|
||||
JS_GETATOM(js,rect.w,v,width,number)
|
||||
JS_GETATOM(js,rect.h,v,height,number)
|
||||
float anchor_x, anchor_y;
|
||||
JS_GETATOM(js, anchor_x, v, anchor_x, number)
|
||||
JS_GETATOM(js, anchor_y, v, anchor_y, number)
|
||||
|
||||
rect.y -= anchor_y*rect.h;
|
||||
rect.x -= anchor_x*rect.w;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
rect transform_rect(SDL_Renderer *ren, rect in, HMM_Mat3 *t)
|
||||
{
|
||||
HMM_Vec3 bottom_left = (HMM_Vec3){in.x,in.y,1.0};
|
||||
@@ -5480,19 +5498,62 @@ static SDL_PixelFormat js2pixelformat(JSContext *js, JSValue v)
|
||||
return it->fmt;
|
||||
}
|
||||
|
||||
typedef struct { const char *name; SDL_ScaleMode mode; } scale_entry;
|
||||
|
||||
static const scale_entry k_scale_table[] = {
|
||||
{ "nearest", SDL_SCALEMODE_NEAREST },
|
||||
{ "linear", SDL_SCALEMODE_LINEAR },
|
||||
{ NULL, SDL_SCALEMODE_LINEAR } /* fallback */
|
||||
};
|
||||
|
||||
static JSValue scalemode2js(JSContext *js, SDL_ScaleMode mode){
|
||||
const scale_entry *it;
|
||||
for(it = k_scale_table; it->name; ++it)
|
||||
if(it->mode == mode) break;
|
||||
return JS_NewString(js, it->name ? it->name : "nearest");
|
||||
}
|
||||
|
||||
static SDL_ScaleMode js2SDL_ScaleMode(JSContext *js, JSValue v){
|
||||
if(JS_IsUndefined(v)) return SDL_SCALEMODE_NEAREST;
|
||||
const char *s = JS_ToCString(js, v);
|
||||
if(!s) return SDL_SCALEMODE_NEAREST;
|
||||
const scale_entry *it;
|
||||
for(it = k_scale_table; it->name; ++it)
|
||||
if(!strcmp(it->name, s)) break;
|
||||
JS_FreeCString(js, s);
|
||||
return it->mode;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* surface.blit(dstrect, src, srcrect, scalemode)
|
||||
* -------------------------------------------------------------------------*/
|
||||
JSC_CCALL(surface_blit,
|
||||
SDL_Surface *dst = js2SDL_Surface(js,self);
|
||||
rect dstrect = js2rect(js,argv[0]);
|
||||
SDL_Surface *src = js2SDL_Surface(js,argv[1]);
|
||||
rect srcrect = js2rect(js,argv[2]);
|
||||
SDL_BlitSurfaceScaled(src, &srcrect, dst, &dstrect, SDL_SCALEMODE_LINEAR);
|
||||
SDL_Surface *dst = js2SDL_Surface(js, self);
|
||||
|
||||
irect dr = {0}, *pdr = NULL;
|
||||
if(!JS_IsUndefined(argv[0])){ dr = js2irect(js, argv[0]); pdr = &dr; }
|
||||
|
||||
SDL_Surface *src = js2SDL_Surface(js, argv[1]);
|
||||
if (!src)
|
||||
return JS_ThrowReferenceError(js, "Argument must be a surface.");
|
||||
|
||||
irect sr = {0}, *psr = NULL;
|
||||
if(!JS_IsUndefined(argv[2])){ sr = js2irect(js, argv[2]); psr = &sr; }
|
||||
|
||||
SDL_ScaleMode mode = js2SDL_ScaleMode(js, argv[3]);
|
||||
|
||||
SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE);
|
||||
|
||||
printf("src %p, dst %p, src rect %g,%g,%g,%g [%p], dst rect %g,%g,%g,%g [%p]\n", src, dst, sr.x, sr.y, sr.w, sr.h, psr, dr.x, dr.y, dr.w, dr.h, pdr);
|
||||
|
||||
SDL_BlitSurfaceScaled(src, psr, dst, pdr, mode);
|
||||
)
|
||||
|
||||
JSC_CCALL(surface_scale,
|
||||
SDL_Surface *src = js2SDL_Surface(js,self);
|
||||
HMM_Vec2 wh = js2vec2(js,argv[0]);
|
||||
SDL_Surface *new = SDL_CreateSurface(wh.x,wh.y, SDL_PIXELFORMAT_RGBA32);
|
||||
SDL_BlitSurfaceScaled(src, NULL, new, NULL, SDL_SCALEMODE_LINEAR);
|
||||
SDL_ScaleMode mode = js2SDL_ScaleMode(js, argv[1]);
|
||||
SDL_Surface *new = SDL_ScaleSurface(src, wh.x, wh.y, mode);
|
||||
ret = SDL_Surface2js(js,new);
|
||||
)
|
||||
|
||||
@@ -5575,38 +5636,38 @@ JSC_CCALL(surface_pixels,
|
||||
SDL_UnlockSurface(surf);
|
||||
)
|
||||
|
||||
JSC_CCALL(surface_width,
|
||||
JSC_CCALL(surface_get_width,
|
||||
SDL_Surface *s = js2SDL_Surface(js,self);
|
||||
return JS_NewFloat64(js, s->w);
|
||||
)
|
||||
|
||||
JSC_CCALL(surface_height,
|
||||
JSC_CCALL(surface_get_height,
|
||||
SDL_Surface *s = js2SDL_Surface(js,self);
|
||||
return JS_NewFloat64(js, s->h);
|
||||
)
|
||||
|
||||
JSC_CCALL(surface_format,
|
||||
JSC_CCALL(surface_get_format,
|
||||
SDL_Surface *s = js2SDL_Surface(js,self);
|
||||
return pixelformat2js(js, s->format);
|
||||
)
|
||||
|
||||
JSC_CCALL(surface_pitch,
|
||||
JSC_CCALL(surface_get_pitch,
|
||||
SDL_Surface *s = js2SDL_Surface(js,self);
|
||||
return JS_NewFloat64(js, s->pitch);
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_SDL_Surface_funcs[] = {
|
||||
MIST_FUNC_DEF(surface, blit, 3),
|
||||
MIST_FUNC_DEF(surface, blit, 4),
|
||||
MIST_FUNC_DEF(surface, scale, 1),
|
||||
MIST_FUNC_DEF(surface, fill,1),
|
||||
MIST_FUNC_DEF(surface, rect,2),
|
||||
MIST_FUNC_DEF(surface, dup, 0),
|
||||
MIST_FUNC_DEF(surface, pixels, 0),
|
||||
MIST_FUNC_DEF(surface, convert, 1),
|
||||
MIST_FUNC_DEF(surface, width, 0),
|
||||
MIST_FUNC_DEF(surface, height, 0),
|
||||
MIST_FUNC_DEF(surface, format, 0),
|
||||
MIST_FUNC_DEF(surface, pitch, 0),
|
||||
JS_CGETSET_DEF("width", js_surface_get_width, NULL),
|
||||
JS_CGETSET_DEF("height", js_surface_get_height, NULL),
|
||||
JS_CGETSET_DEF("format", js_surface_get_format, NULL),
|
||||
JS_CGETSET_DEF("pitch", js_surface_get_pitch, NULL),
|
||||
};
|
||||
|
||||
JSC_CCALL(camera_frame,
|
||||
@@ -6572,6 +6633,34 @@ JSC_CCALL(os_make_texture,
|
||||
ret = SDL_Surface2js(js,surf);
|
||||
)
|
||||
|
||||
JSC_CCALL(graphics_surface_from_pixels,
|
||||
int w, h, pitch;
|
||||
SDL_PixelFormat fmt;
|
||||
JS_GETATOM(js, w, argv[0], width, number)
|
||||
JS_GETATOM(js, h, argv[0], height, number)
|
||||
JS_GETATOM(js, fmt, argv[0], format, pixelformat)
|
||||
JS_GETATOM(js, pitch, argv[0], pitch, number)
|
||||
JSValue js_px = JS_GetPropertyStr(js, argv[0], "buffer");
|
||||
|
||||
size_t len;
|
||||
void *raw = JS_GetArrayBuffer(js, &len, js_px);
|
||||
|
||||
JS_FreeValue(js, js_px);
|
||||
|
||||
if (!raw || !w || !h || !fmt)
|
||||
return JS_ThrowReferenceError(js, "Invalid source.");
|
||||
|
||||
void *newraw = malloc(len);
|
||||
memcpy(newraw, raw, len);
|
||||
|
||||
SDL_Surface *s = SDL_CreateSurfaceFrom(w, h, fmt, newraw, pitch);
|
||||
|
||||
if (!s)
|
||||
return JS_ThrowInternalError(js, "Unable to create surface: %s", SDL_GetError());
|
||||
|
||||
return SDL_Surface2js(js, s);
|
||||
)
|
||||
|
||||
// input: (gif image data)
|
||||
JSC_CCALL(os_make_gif,
|
||||
size_t rawlen;
|
||||
@@ -6622,7 +6711,6 @@ CLEANUP:
|
||||
free(pixels);
|
||||
)
|
||||
|
||||
|
||||
JSValue aseframe2js(JSContext *js, ase_frame_t aframe)
|
||||
{
|
||||
JSValue frame = JS_NewObject(js);
|
||||
@@ -6630,7 +6718,6 @@ JSValue aseframe2js(JSContext *js, ase_frame_t aframe)
|
||||
memcpy(frame_pixels, aframe.pixels, aframe.ase->w*aframe.ase->h*4);
|
||||
SDL_Surface *surf = SDL_CreateSurfaceFrom(aframe.ase->w, aframe.ase->h, SDL_PIXELFORMAT_RGBA32, frame_pixels, aframe.ase->w*4);
|
||||
JS_SetPropertyStr(js, frame, "surface", SDL_Surface2js(js,surf));
|
||||
JS_SetPropertyStr(js, frame, "rect", rect2js(js,(rect){.x=0,.y=0,.w=1,.h=1}));
|
||||
JS_SetPropertyStr(js, frame, "time", number2js(js,(float)aframe.duration_milliseconds/1000.0));
|
||||
return frame;
|
||||
}
|
||||
@@ -7312,6 +7399,21 @@ JSC_CCALL(graphics_save_png,
|
||||
return JS_ThrowInternalError(js, "Could not write png");
|
||||
)
|
||||
|
||||
JSC_CCALL(graphics_save_jpg,
|
||||
const char *file = JS_ToCString(js, argv[0]);
|
||||
int w, h, comp, pitch, quality;
|
||||
JS_ToInt32(js, &w, argv[1]);
|
||||
JS_ToInt32(js, &h, argv[2]);
|
||||
JS_ToInt32(js, &pitch, argv[4]);
|
||||
JS_ToInt32(js, &quality, argv[5]);
|
||||
if (!quality) quality = 80;
|
||||
size_t size;
|
||||
void *data = JS_GetArrayBuffer(js, &size, argv[3]);
|
||||
|
||||
if (!stbi_write_jpg(file, w, h, 4, data, quality))
|
||||
return JS_ThrowInternalError(js, "Could not write png");
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_graphics_funcs[] = {
|
||||
MIST_FUNC_DEF(gpu, make_sprite_mesh, 2),
|
||||
MIST_FUNC_DEF(gpu, make_sprite_queue, 4),
|
||||
@@ -7329,6 +7431,8 @@ static const JSCFunctionListEntry js_graphics_funcs[] = {
|
||||
MIST_FUNC_DEF(os, make_line_prim, 5),
|
||||
MIST_FUNC_DEF(graphics, hsl_to_rgb, 3),
|
||||
MIST_FUNC_DEF(graphics, save_png, 4),
|
||||
MIST_FUNC_DEF(graphics, save_jpg, 4),
|
||||
MIST_FUNC_DEF(graphics, surface_from_pixels, 1),
|
||||
};
|
||||
|
||||
static const JSCFunctionListEntry js_video_funcs[] = {
|
||||
|
||||
@@ -101,6 +101,7 @@ static JSValue js_qr_encode(JSContext *js, JSValueConst this_val, int argc, JSVa
|
||||
must_free_data = 1;
|
||||
} else {
|
||||
/* treat everything else as raw bytes */
|
||||
use_byte_mode = 1;
|
||||
uint8_t *buf = JS_GetArrayBuffer(js, &data_len, argv[0]);
|
||||
if (!buf) {
|
||||
return JS_ThrowTypeError(js, "encode expects a string or ArrayBuffer");
|
||||
@@ -123,6 +124,7 @@ static JSValue js_qr_encode(JSContext *js, JSValueConst this_val, int argc, JSVa
|
||||
|
||||
// qrcode->width is the size of one side
|
||||
// qrcode->data is width * width bytes
|
||||
printf("Encoded %d of data into a QR of width %d\n", data_len, qrcode->width);
|
||||
int width = qrcode->width;
|
||||
size_t size = (size_t)width * width; // total modules
|
||||
|
||||
@@ -204,17 +206,17 @@ static JSValue js_qr_decode(JSContext *js, JSValueConst this_val, int argc, JSVa
|
||||
int count = quirc_count(qr);
|
||||
JSValue result = JS_NewArray(js);
|
||||
|
||||
printf("found %d codes\n", count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
struct quirc_code code;
|
||||
struct quirc_data qdata;
|
||||
|
||||
quirc_extract(qr, i, &code);
|
||||
printf("code is %p\n", code.size);
|
||||
if (quirc_decode(&code, &qdata) == QUIRC_SUCCESS) {
|
||||
int err = quirc_decode(&code, &qdata);
|
||||
if (err == QUIRC_SUCCESS) {
|
||||
JSValue item = JS_NewArrayBufferCopy(js, qdata.payload, qdata.payload_len);
|
||||
// JSValue item = JS_NewStringLen(js, (const char *)qdata.payload, qdata.payload_len);
|
||||
JS_SetPropertyUint32(js, result, i, item);
|
||||
} else {
|
||||
printf("QR error: %s\n", quirc_strerror(err));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,6 +281,7 @@ static JSValue js_qr_rgba(JSContext *js, JSValueConst self, int argc, JSValueCon
|
||||
JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, h));
|
||||
JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, pitch));
|
||||
JS_SetPropertyStr(js, obj, "buffer", ab);
|
||||
JS_SetPropertyStr(js, obj, "format", JS_NewString(js, "rgba32"));
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ struct rect {
|
||||
};
|
||||
|
||||
typedef SDL_FRect rect;
|
||||
typedef SDL_Rect irect;
|
||||
|
||||
float *rgba2floats(float *r, struct rgba c);
|
||||
|
||||
|
||||
103
tests/animation.js
Normal file
103
tests/animation.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/* anim.js – drop this at top of your script or in a module */
|
||||
var Anim = (() => {
|
||||
const DEFAULT_MIN = 1 / 60; /* 16 ms – one frame */
|
||||
|
||||
function play(source, loop=true){
|
||||
return {
|
||||
src : source,
|
||||
idx : 0,
|
||||
timer : 0,
|
||||
loop : loop ?? source.loop ?? true
|
||||
};
|
||||
}
|
||||
|
||||
function update(a, dt){
|
||||
a.timer += dt;
|
||||
const frames = a.src.frames;
|
||||
while(true){
|
||||
const time = Math.max(frames[a.idx].time || 0, Anim.minDelay);
|
||||
if(a.timer < time) break; /* still on current frame */
|
||||
|
||||
a.timer -= time;
|
||||
a.idx += 1;
|
||||
|
||||
if(a.idx >= frames.length){
|
||||
if(a.loop) a.idx = 0;
|
||||
else { a.idx = frames.length - 1; a.timer = 0; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function current(a){ return a.src.frames[a.idx].image; }
|
||||
function updateAll(arr, dt){ for(const a of arr) update(a, dt); }
|
||||
function draw(a, pos, opt, pipe){
|
||||
draw2d.image(current(a), pos, 0, [0,0], [0,0], opt, pipe);
|
||||
}
|
||||
|
||||
return {play, update, current, updateAll, draw, minDelay:DEFAULT_MIN};
|
||||
})();
|
||||
|
||||
var render = use('render');
|
||||
/* ── init window ───────────────────────── */
|
||||
render.initialize({width:500, height:500, resolution_x:500, resolution_y:500,
|
||||
mode:'letterboxed', refresh:60});
|
||||
|
||||
var os = use('os');
|
||||
var draw2d = use('draw2d');
|
||||
var gfx = use('graphics');
|
||||
|
||||
var camera = {
|
||||
size: [500,500],
|
||||
transform: os.make_transform(),
|
||||
fov:50,
|
||||
near_z: 0,
|
||||
far_z: 1000,
|
||||
surface: undefined,
|
||||
viewport: {x:0,y:0,width:1,height:1},
|
||||
ortho:true,
|
||||
anchor:[0,0],
|
||||
}
|
||||
|
||||
/* ── load animations ───────────────────── */
|
||||
const crab = gfx.texture('tests/crab'); // gif → Animation
|
||||
const warrior = gfx.texture('tests/warrior'); // ase → {Original:Animation}
|
||||
|
||||
const anims = [
|
||||
Anim.play(crab), // crab.frames
|
||||
Anim.play(warrior.Run) // warrior.Original.frames
|
||||
];
|
||||
|
||||
/* ── fps probe vars ───────────────────── */
|
||||
var fpsTimer=0, fpsCount=0
|
||||
|
||||
Anim.minDelay = 1 / 100; // 10 ms, feel free to tune later
|
||||
|
||||
let last = os.now();
|
||||
|
||||
function loop(){
|
||||
const now = os.now();
|
||||
const dt = now - last; // real frame time
|
||||
last = now;
|
||||
|
||||
Anim.updateAll(anims, dt);
|
||||
|
||||
/* draw */
|
||||
render.clear([22/255,120/255,194/255,255/255]);
|
||||
render.camera(camera);
|
||||
Anim.draw(anims[0], [ 50,200]);
|
||||
Anim.draw(anims[1], [250,200]);
|
||||
render.present();
|
||||
|
||||
/* fps probe (unchanged) */
|
||||
fpsTimer += dt; fpsCount++;
|
||||
if(fpsTimer >= 0.5){
|
||||
prosperon.window.title =
|
||||
`Anim demo FPS ${(fpsCount/fpsTimer).toFixed(1)}`;
|
||||
fpsTimer = fpsCount = 0;
|
||||
}
|
||||
|
||||
/* schedule next tick: aim for 60 Hz but won’t matter to anim speed */
|
||||
$_.delay(loop, Math.max(0, (1/60) - (os.now()-now)));
|
||||
}
|
||||
loop();
|
||||
|
||||
@@ -49,7 +49,7 @@ function hsl_to_rgb(h, s, l) {
|
||||
return [r + m, g + m, b + m, 1] // 0‒1 floats, alpha = 1
|
||||
}
|
||||
|
||||
var bunny_count = 2000
|
||||
var bunny_count = 20
|
||||
|
||||
for (var i = 0; i < bunny_count; i++) {
|
||||
var pct = i/bunny_count
|
||||
|
||||
BIN
tests/crab.gif
Normal file
BIN
tests/crab.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 124 KiB |
105
tests/qr_drag.js
105
tests/qr_drag.js
@@ -28,7 +28,7 @@ var sprite = use('lcdsprite')
|
||||
var graphics = use('graphics')
|
||||
|
||||
var dt = 0
|
||||
var img = undefined
|
||||
var img = "alcinaqr"
|
||||
|
||||
var ioguy = {
|
||||
__ACTORDATA__: { id: os.ioactor() }
|
||||
@@ -36,25 +36,62 @@ var ioguy = {
|
||||
|
||||
var io = use('io')
|
||||
io.mount('/')
|
||||
var miniz = use('miniz')
|
||||
|
||||
var nota = use('nota')
|
||||
|
||||
var qr = use('qr')
|
||||
|
||||
var myimg = qr.encode("HELLO WORLD")
|
||||
var mybytes = qr.rgba(myimg)
|
||||
|
||||
graphics.save_png("qr_hello.png", mybytes.width, mybytes.height, mybytes.buffer, mybytes.pitch);
|
||||
|
||||
// test saving a zipped json
|
||||
var miniz = use('miniz')
|
||||
|
||||
var lvl = io.slurp('tests/level.json')
|
||||
var lvl_json = lvl
|
||||
console.log(`json size is ${lvl.length}`)
|
||||
lvl = json.decode(lvl)
|
||||
lvl = nota.encode(lvl)
|
||||
console.log(`nota size is ${lvl.byteLength}`)
|
||||
var lvl_cmp = miniz.compress(lvl)
|
||||
var lvl_json_cmp = miniz.compress(lvl_json)
|
||||
|
||||
console.log(`compressed json is ${lvl_json_cmp.byteLength}`)
|
||||
console.log(`compressed nota is ${lvl_cmp.byteLength}`)
|
||||
|
||||
var uncmp = miniz.decompress(lvl_cmp, false)
|
||||
console.log(uncmp.byteLength)
|
||||
|
||||
console.log(`json cmp width: ${qr.encode(lvl_json_cmp).width}`)
|
||||
var qr_lvl = qr.encode(lvl_cmp)
|
||||
console.log(`nota cmp width: ${qr_lvl.width}`)
|
||||
var lvl_bytes = qr.rgba(qr_lvl)
|
||||
graphics.save_png("qr_level.png", lvl_bytes.width, lvl_bytes.height, lvl_bytes.buffer, lvl_bytes.pitch)
|
||||
|
||||
console.log(`saved compressed level as qr; size was ${lvl_cmp.byteLength}`)
|
||||
console.log(lvl_bytes.buffer.byteLength)
|
||||
console.log(json.encode(lvl_bytes))
|
||||
|
||||
var bsurf = graphics.surface_from_pixels(lvl_bytes)
|
||||
|
||||
var frame_img = graphics.texture("alcinaqr")
|
||||
|
||||
var blit_img = graphics.from_surface("frame", frame_img.cpu.dup())
|
||||
|
||||
var qr_size = lvl_bytes.width*4
|
||||
console.log(`blowing it up to ${qr_size}`)
|
||||
var qr_rect = {x:300, y:500, width:qr_size, height:qr_size}
|
||||
var gutter = 25 // pixels per side
|
||||
var qr_rect_gutter = {
|
||||
x: qr_rect.x - gutter,
|
||||
y: qr_rect.y - gutter,
|
||||
width: qr_rect.width + gutter*2,
|
||||
height: qr_rect.height + gutter*2
|
||||
}
|
||||
blit_img.cpu.blit(qr_rect, bsurf, undefined, "nearest")
|
||||
|
||||
graphics.save_png("blit.png", blit_img.cpu.width, blit_img.cpu.height, blit_img.cpu.pixels(), blit_img.cpu.pitch)
|
||||
|
||||
graphics.save_jpg("blit.png", blit_img.cpu.width, blit_img.cpu.height, blit_img.cpu.pixels(), 1)
|
||||
|
||||
graphics.save_png("qrblit.png", bsurf.width, bsurf.height, bsurf.pixels(), bsurf.pitch)
|
||||
|
||||
var qr_img = graphics.from_surface("qr", bsurf)
|
||||
|
||||
img = frame_img
|
||||
|
||||
$_.send(ioguy, {
|
||||
type: "subscribe",
|
||||
@@ -81,6 +118,15 @@ function parse_data(res)
|
||||
img = graphics.texture_from_data(res.data)
|
||||
}
|
||||
|
||||
function extract_qr_surface(surface)
|
||||
{
|
||||
var qr = graphics.make_surface([qr_rect_gutter.width, qr_rect_gutter.height])
|
||||
qr.blit(undefined, surface, qr_rect_gutter)
|
||||
return qr
|
||||
}
|
||||
|
||||
var display = undefined
|
||||
|
||||
$_.receiver(e => {
|
||||
if (e.type === 'quit')
|
||||
os.exit()
|
||||
@@ -88,15 +134,25 @@ $_.receiver(e => {
|
||||
switch(e.type) {
|
||||
case "drop_file":
|
||||
console.log(`got ${e.data} dropped`)
|
||||
break
|
||||
img = e.data
|
||||
var image = graphics.texture(e.data)
|
||||
var data = qr.decode(image.surface.pixels(), image.surface.width(), image.surface.height(), image.surface.pitch())
|
||||
console.log(`found ${data.length} qr`)
|
||||
var data = io.slurpbytes(e.data)
|
||||
img = graphics.make_texture(data)
|
||||
var qr_surf = img;//extract_qr_surface(img)
|
||||
var qr_surf_scaled = qr_surf.scale([qr_surf.width/4, qr_surf.height/4])
|
||||
var image = {surface:qr_surf_scaled}
|
||||
display = graphics.from_surface("aaa", qr_surf_scaled)
|
||||
var data = qr.decode(image.surface.pixels(), image.surface.width, image.surface.height, image.surface.pitch)
|
||||
console.log(`found ${data.length} qr codes`)
|
||||
if (data.length == 0) break
|
||||
data = data[0]
|
||||
console.log(data.byteLength)
|
||||
var ddata = miniz.decompress(data, true)
|
||||
console.log(ddata)
|
||||
var ddata = miniz.decompress(data, false)
|
||||
console.log(ddata.byteLength)
|
||||
console.log(`qr data size was ${data.byteLength}, uncompressed ${ddata.byteLength}`)
|
||||
|
||||
var nn = nota.decode(ddata)
|
||||
console.log(json.encode(nn))
|
||||
break;
|
||||
|
||||
case "drop_text":
|
||||
console.log(`text ${e.data} dropped`)
|
||||
// if e.data is a url, fetch it
|
||||
@@ -104,8 +160,7 @@ $_.receiver(e => {
|
||||
console.log('fetching!')
|
||||
http.fetch(e.data, parse_data)
|
||||
}
|
||||
|
||||
break
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
@@ -116,8 +171,14 @@ function loop()
|
||||
render.clear([22/255,120/255,194/255,255/255])
|
||||
render.camera(camera)
|
||||
|
||||
if (img)
|
||||
draw.image(img, {x:0,y:0,width:300,height:300})
|
||||
draw.image(frame_img, {x:20,y:100, width:200,height:300})
|
||||
draw.image(qr_img, {x:400, y:100})
|
||||
draw.image(blit_img, {x:250, y:0, width:100, height: 150})
|
||||
if (display)
|
||||
draw.image(display, {x:0,y:0,width:200,height:200})
|
||||
|
||||
// if (img)
|
||||
// draw.image(img, {x:0,y:0,width:300,height:300})
|
||||
|
||||
render.present()
|
||||
|
||||
|
||||
BIN
tests/warrior.aseprite
Normal file
BIN
tests/warrior.aseprite
Normal file
Binary file not shown.
Reference in New Issue
Block a user