From 92f56570d9d439fc2d5e390e296975f75bc467bf Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 6 May 2025 12:48:13 -0500 Subject: [PATCH] fix qr decoding --- scripts/modules/lcdsprite.js | 65 ++++---- scripts/modules/sdl_render.js | 4 +- source/jsffi.c | 272 +++++++++++++++++++++++++++------- source/prosperon.c | 1 + source/qjs_qr.c | 112 ++++++++------ source/sprite.c | 21 ++- source/sprite.h | 3 +- tests/bunnymark.js | 23 +-- tests/qr_drag.js | 76 ++++++++++ 9 files changed, 421 insertions(+), 156 deletions(-) create mode 100644 tests/qr_drag.js diff --git a/scripts/modules/lcdsprite.js b/scripts/modules/lcdsprite.js index 0122d02c..bf0260b6 100644 --- a/scripts/modules/lcdsprite.js +++ b/scripts/modules/lcdsprite.js @@ -1,12 +1,3 @@ -/* Sprite façade (JS) ↔ struct sprite (C) - * The C side exposes per-field getters/setters plus - * `set_affine(m)` which (re)calculates the 2×2 matrix from - * scale / skew / rotation. - * We mirror every exposed field and make sure the C - * matrix is refreshed whenever one of the three factors - * changes. - */ - var sprite = {} var graphics = use('graphics') @@ -20,6 +11,7 @@ var SCALE = Symbol() var SKEW = Symbol() var CENTER = Symbol() var COLOR = Symbol() +var IMAGE = Symbol() var ursprite = { get pos() { return this[POS] }, @@ -45,7 +37,7 @@ var ursprite = { set scale(v){ this[SCALE] = v this[SPRITE].scale = v - this[SPRITE].set_affine() + this[SPRITE].set_affine() }, get skew() { return this[SKEW] }, @@ -64,36 +56,35 @@ var ursprite = { this[SPRITE].color = v }, - move(mv){ - this.pos = {x:this.pos.x+mv.x, y:this.pos.y+mv.y} - }, + move(mv) { this[SPRITE].move(mv) }, moveto(p){ this.pos = p + }, + + get image() { return this[IMAGE] }, + set image(img) { + this[IMAGE] = img + this[SPRITE].set_image(img) } } var _sprites = [] -sprite.create = function(image, pos, anchor=[0,0], layer=0, props={}) { - var sp = Object.create(ursprite) - var raw = graphics.make_sprite() +var def_sprite = Object.freeze({ + pos: [0,0], + rotation: 0, + scale: [1,1], + center: [0,0], + color: [1,1,1,1], + skew: [0,0], + layer: 0, +}) - sp[SPRITE] = raw - - sp.pos = pos - sp.rotation = 0 - sp.scale = [1,1] - sp.skew = [0,0] - sp.center = anchor - sp.color = [1,1,1,1] - - var tex = graphics.texture(image) - raw.set_image(tex) - sp.image = tex - - sp.layer = layer - sp.props = props - sp.anchor = anchor +sprite.create = function(image, info) { + info.__proto__ = def_sprite + var sp = Object.create(ursprite) + sp[SPRITE] = graphics.make_sprite(info) + sp.image = graphics.texture(image) _sprites.push(sp) return sp @@ -102,6 +93,14 @@ sprite.create = function(image, pos, anchor=[0,0], layer=0, props={}) { sprite.forEach = fn => { for (let s of _sprites) fn(s) } sprite.values = () => _sprites.slice() sprite.geometry= () => graphics.make_sprite_mesh(_sprites) -sprite.queue = () => graphics.make_sprite_queue(_sprites) + +var raws = [] +sprite.queue = function() { + if (raws.length != _sprites.length) + raws.length = _sprites.length + + for (var i = 0; i < _sprites.length; i++) raws[i] = _sprites[i] + return graphics.make_sprite_queue(_sprites.map(x => x[SPRITE])) +} return sprite diff --git a/scripts/modules/sdl_render.js b/scripts/modules/sdl_render.js index 9d59a8d6..b864f525 100644 --- a/scripts/modules/sdl_render.js +++ b/scripts/modules/sdl_render.js @@ -53,8 +53,8 @@ var current_color = Color.white render.image = function(image, rect, rotation, anchor, shear, info) { - rect.width = image.rect_px.width; - rect.height = image.rect_px.height; +// rect.width = image.rect_px.width; +// rect.height = image.rect_px.height; context.texture(image.texture, image.rect_px, rect, rotation, anchor); } diff --git a/source/jsffi.c b/source/jsffi.c index 9cd0858f..2b575a2a 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -3147,8 +3147,10 @@ JSC_CCALL(renderer_sprite, sprite sp = js_getsprite(js, argv[0]); SDL_Texture *tex; JS_GETATOM(js, tex, sp.image, texture, SDL_Texture); + rect uv; + JS_GETATOM(js, uv, sp.image, rect, rect); - float w = sp.uv.w, h = sp.uv.h; + float w = uv.w, h = uv.h; HMM_Vec2 tl_local = { -sp.center.X, -sp.center.Y }; HMM_Vec2 tr_local = { -sp.center.X + w, -sp.center.Y }; @@ -3162,7 +3164,7 @@ JSC_CCALL(renderer_sprite, SDL_FPoint right = renderer_world_to_screen(ctx, world_tr); SDL_FPoint down = renderer_world_to_screen(ctx, world_bl); - if (!SDL_RenderTextureAffine(ctx->sdl, tex, &sp.uv, &origin, &right, &down)) + if (!SDL_RenderTextureAffine(ctx->sdl, tex, &uv, &origin, &right, &down)) return JS_ThrowInternalError(js, "Render sprite error: %s", SDL_GetError()); ) @@ -4146,6 +4148,9 @@ JSC_CCALL(gpu_make_sprite_queue, JS_DupValue(js,sprites[i].image); } else { quads = js_arrlen(js, argv[0]); + if (quads == 0) + return JS_ThrowReferenceError(js, "Expected an array of sprites with length > 0."); + arrsetcap(sprites, quads); for (int i = 0; i < quads; i++) { @@ -4161,10 +4166,10 @@ JSC_CCALL(gpu_make_sprite_queue, JS_GETATOM(js, sp.center, sub, center, vec2) JS_GETATOM(js, sp.skew, sub, skew, vec2) JS_GETATOM(js, sp.scale, sub, scale, vec2) - JS_GETATOM(js,sp.color,sub,color,color) - JS_GETATOM(js,sp.layer,sub,layer,number) - JS_GETATOM(js,sp.uv,sub,src,rect) + JS_GETATOM(js, sp.color,sub,color,color) + JS_GETATOM(js, sp.layer,sub,layer,number) sp.image = JS_GetProperty(js,sub,image); + sprite_apply(&sp); arrput(sprites,sp); } JS_FreeValue(js, sub); @@ -4174,28 +4179,50 @@ JSC_CCALL(gpu_make_sprite_queue, qsort(sprites, quads, sizeof(sprite), sort_sprite); struct quad_buffers buffers = quad_buffers_new(quads*4); + + const HMM_Vec2 local[4] = { {0,0}, {1,0}, {0,1}, {1,1} }; + + rect uv; + rect uv_px; + JSValue cur_img = JS_UNDEFINED; - for (int i = 0; i < quads; i++) { - rect pr = {0}; - rect uv = sprites[i].uv; - HMM_Vec4 c = sprites[i].color; - - int idx = i * 4; - - buffers.pos[idx + 0] = (HMM_Vec2){ pr.x, pr.y }; - buffers.pos[idx + 1] = (HMM_Vec2){ pr.x+pr.w, pr.y }; - buffers.pos[idx + 2] = (HMM_Vec2){ pr.x, pr.y+pr.h }; - buffers.pos[idx + 3] = (HMM_Vec2){ pr.x+pr.w, pr.y+pr.h }; - - buffers.uv[idx + 0] = (HMM_Vec2){ uv.x, uv.y+uv.h }; - buffers.uv[idx + 1] = (HMM_Vec2){ uv.x+uv.w, uv.y+uv.h }; - buffers.uv[idx + 2] = (HMM_Vec2){ uv.x, uv.y }; - buffers.uv[idx + 3] = (HMM_Vec2){ uv.x+uv.w, uv.y }; - - buffers.color[idx + 0] = c; - buffers.color[idx + 1] = c; - buffers.color[idx + 2] = c; - buffers.color[idx + 3] = c; + for (size_t i = 0; i < quads; i++) { + sprite *s = &sprites[i]; + if (JS_IsUndefined(cur_img) || !JS_StrictEq(js, s->image, cur_img)) { + cur_img = s->image; + JS_GETATOM(js, uv, cur_img, rect, rect) + JS_GETATOM(js, uv_px, cur_img, rect_px, rect) + } + + HMM_Vec2 px_size = { + uv_px.w * s->scale.X, + uv_px.h * s->scale.Y + }; + + HMM_Vec2 anchor = { + px_size.X * s->center.X, + px_size.Y * s->center.Y + }; + + size_t base = i * 4; + + for (int v = 0; v < 4; v++) { + HMM_Vec2 lp = { + local[v].X * px_size.X - anchor.X, + local[v].Y * px_size.Y - anchor.Y + }; + + HMM_Vec2 world = HMM_AddV2(s->pos, HMM_MulM2V2(s->affine, lp)); + + buffers.pos[base + v] = world; + buffers.color[base + v] = s->color; + } + + /* UVs are still top-left-origin pixel coords, so keep previous packing */ + buffers.uv[base + 0] = (HMM_Vec2){ uv.x, uv.y + uv.h }; + buffers.uv[base + 1] = (HMM_Vec2){ uv.x + uv.w, uv.y + uv.h }; + buffers.uv[base + 2] = (HMM_Vec2){ uv.x, uv.y }; + buffers.uv[base + 3] = (HMM_Vec2){ uv.x + uv.w, uv.y }; } JSValue mesh = quadbuffers_to_mesh(js, buffers); @@ -5407,6 +5434,51 @@ static const JSCFunctionListEntry js_SDL_GPUComputePass_funcs[] = { MIST_FUNC_DEF(compute, storage_textures, 2), }; +typedef struct { const char *name; SDL_PixelFormat fmt; } fmt_entry; + +static const fmt_entry k_fmt_table[] = { + { "rgba32", SDL_PIXELFORMAT_RGBA32 }, + { "argb32", SDL_PIXELFORMAT_ARGB32 }, + { "bgra32", SDL_PIXELFORMAT_BGRA32 }, + { "abgr32", SDL_PIXELFORMAT_ABGR32 }, + { "rgb565", SDL_PIXELFORMAT_RGB565 }, + { "bgr565", SDL_PIXELFORMAT_BGR565 }, + { "rgb24", SDL_PIXELFORMAT_RGB24 }, + { "bgr24", SDL_PIXELFORMAT_BGR24 }, + { "rgb332", SDL_PIXELFORMAT_RGB332 }, + { "rgba64", SDL_PIXELFORMAT_RGBA64 }, + { "rgb48", SDL_PIXELFORMAT_RGB48 }, + { NULL, SDL_PIXELFORMAT_UNKNOWN } +}; + +static JSValue pixelformat2js(JSContext *js, SDL_PixelFormat fmt) +{ + fmt_entry *it; + for (it = k_fmt_table; it->name; it++) + if (it->fmt == fmt) + break; + + if (it->name) + return JS_NewString(js, it->name); + + return JS_UNDEFINED; +} + +static SDL_PixelFormat js2pixelformat(JSContext *js, JSValue v) +{ + if (JS_IsUndefined(v)) return SDL_PIXELFORMAT_UNKNOWN; + const char *s = JS_ToCString(js, v); + if (!s) return SDL_PIXELFORMAT_UNKNOWN; + + fmt_entry *it; + for (it = k_fmt_table; it->name; it++) + if (!strcmp(it->name, s)) + break; + + JS_FreeCString(js,s); + return it->fmt; +} + JSC_CCALL(surface_blit, SDL_Surface *dst = js2SDL_Surface(js,self); rect dstrect = js2rect(js,argv[0]); @@ -5461,21 +5533,79 @@ JSC_CCALL(surface_rect, SDL_FillSurfaceRect(dst,&r,SDL_MapRGBA(&pdetails,NULL, color.r*255,color.g*255,color.b*255,color.a*255)); ) +JSC_CCALL(surface_convert, + SDL_Surface *surf = js2SDL_Surface(js,self); + SDL_PixelFormat fmt = js2pixelformat(js, argv[0]); + SDL_Surface *dst = SDL_ConvertSurface(surf, fmt); + if (!dst) return JS_ThrowInternalError(js, "Convert failed: %s", SDL_GetError()); + + return SDL_Surface2js(js, dst); +) + JSC_CCALL(surface_dup, SDL_Surface *surf = js2SDL_Surface(js,self); - SDL_Surface *conv = SDL_ConvertSurface(surf, SDL_PIXELFORMAT_RGBA8888); + SDL_Surface *conv = SDL_DuplicateSurface(surf); if (!conv) return JS_ThrowReferenceError(js, "could not blit to dup'd surface: %s", SDL_GetError()); return SDL_Surface2js(js,conv); ) +JSC_CCALL(surface_pixels, + SDL_Surface *surf = js2SDL_Surface(js,self); + +/* if (SDL_ISPIXELFORMAT_FOURCC(surf->format->format)) + return JS_ThrowTypeError(js, "planar or FOURCC formats are not supported - convert first"); +*/ + + int locked = 0; + if (SDL_MUSTLOCK(surf)) { + if (SDL_LockSurface(surf) < 0) + return JS_ThrowInternalError(js, "Lock surface failed: %s", SDL_GetError()); + + locked = 1; + } + + size_t byte_size = surf->pitch*surf->h; + + ret = JS_NewArrayBufferCopy(js, surf->pixels, byte_size); + + if (locked) + SDL_UnlockSurface(surf); +) + +JSC_CCALL(surface_width, + SDL_Surface *s = js2SDL_Surface(js,self); + return JS_NewFloat64(js, s->w); +) + +JSC_CCALL(surface_height, + SDL_Surface *s = js2SDL_Surface(js,self); + return JS_NewFloat64(js, s->h); +) + +JSC_CCALL(surface_format, + SDL_Surface *s = js2SDL_Surface(js,self); + return pixelformat2js(js, s->format); +) + +JSC_CCALL(surface_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, 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), }; JSC_CCALL(camera_frame, @@ -6419,22 +6549,20 @@ JSC_CCALL(os_make_texture, 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, 0); + void *data = stbi_load_from_memory(raw, len, &width, &height, &n, 4); - if (data == NULL) - return JS_ThrowReferenceError(js, "no known image type from pixel data"); - - int FMT = 0; - if (n == 4) - FMT = SDL_PIXELFORMAT_RGBA32; - else if (n == 3) - FMT = SDL_PIXELFORMAT_RGB24; - else { - free(data); - return JS_ThrowReferenceError(js, "unknown pixel format. got %d channels", n); - } + if (!data) + return JS_ThrowReferenceError(js, "no known image type from pixel data: %s", stbi_failure_reason()); - SDL_Surface *surf = SDL_CreateSurfaceFrom(width,height,FMT, data, width*n); + if (width <= 0 || height <= 0) { + free(data); + return JS_ThrowReferenceError(js, "decoded image has invalid size: %dx%d", width, height); + } + + int pitch = width*4; + int fmt = SDL_PIXELFORMAT_RGBA32; + + SDL_Surface *surf = SDL_CreateSurfaceFrom(width,height,fmt, data, pitch); if (!surf) { free(data); return JS_ThrowReferenceError(js, "Error creating surface from data: %s",SDL_GetError()); @@ -6592,7 +6720,18 @@ JSC_CCALL(os_make_transform, // t->self = JS_DupValue(js,ret); t->self = ret; ) -JSC_CCALL(os_make_sprite, return sprite2js(js,make_sprite())) +JSC_CCALL(os_make_sprite, + sprite *sp = make_sprite(); + JS_GETATOM(js, sp->pos, argv[0], pos, vec2) + JS_GETATOM(js, sp->center, argv[0], center, vec2) + JS_GETATOM(js, sp->skew, argv[0], skew, vec2) + JS_GETATOM(js, sp->scale, argv[0], scale, vec2) + JS_GETATOM(js, sp->rotation, argv[0], rotation, angle) + JS_GETATOM(js, sp->layer, argv[0], layer, number) + JS_GETATOM(js, sp->color, argv[0], color, color) + sprite_apply(sp); + return sprite2js(js,sp); +) JSC_SCALL(os_system, ret = number2js(js,system(str)); ) @@ -7139,6 +7278,26 @@ static const JSCFunctionListEntry js_util_funcs[] = { MIST_FUNC_DEF(os, insertion_sort, 2), }; +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}); +) + static const JSCFunctionListEntry js_graphics_funcs[] = { MIST_FUNC_DEF(gpu, make_sprite_mesh, 2), MIST_FUNC_DEF(gpu, make_sprite_queue, 4), @@ -7152,8 +7311,9 @@ static const JSCFunctionListEntry js_graphics_funcs[] = { MIST_FUNC_DEF(os, make_surface, 1), MIST_FUNC_DEF(os, make_cursor, 1), MIST_FUNC_DEF(os, make_font, 2), - MIST_FUNC_DEF(os, make_sprite, 0), + MIST_FUNC_DEF(os, make_sprite, 1), MIST_FUNC_DEF(os, make_line_prim, 5), + MIST_FUNC_DEF(graphics, hsl_to_rgb, 3), }; static const JSCFunctionListEntry js_video_funcs[] = { @@ -7362,6 +7522,13 @@ JSC_GETSET(sprite, skew, vec2) JSC_GETSET(sprite, scale, vec2) JSC_GETSET(sprite, rotation, number) +JSC_CCALL(sprite_move, + sprite *sp = js2sprite(js,self); + HMM_Vec2 mv = js2vec2(js,argv[0]); + sp->pos.x += mv.x; + sp->pos.y += mv.y; +) + JSC_CCALL(sprite_set_affine, sprite *sp = js2sprite(js,self); sprite_apply(sp); @@ -7371,21 +7538,20 @@ JSC_CCALL(sprite_set_image, sprite *sp = js2sprite(js,self); if (!JS_IsUndefined(sp->image)) JS_FreeValue(js,sp->image); - sp->image = JS_DupValue(js,argv[0]); - if (JS_IsUndefined(sp->image)) return JS_UNDEFINED; - JSValue img = sp->image; - JS_GETATOM(js, sp->uv, img, rect, rect) + sp->image = JS_DupValue(js, argv[0]); ) static const JSCFunctionListEntry js_sprite_funcs[] = { MIST_FUNC_DEF(sprite, set_affine, 0), MIST_FUNC_DEF(sprite, set_image, 1), - CGETSET_ADD(sprite, skew), - CGETSET_ADD(sprite, rotation), - CGETSET_ADD(sprite, pos), - CGETSET_ADD(sprite, center), - CGETSET_ADD(sprite, layer), - CGETSET_ADD(sprite, color), + MIST_FUNC_DEF(sprite, move, 1), + JS_CGETSET_DEF("pos", js_sprite_get_pos, js_sprite_set_pos), + JS_CGETSET_DEF("scale", js_sprite_get_scale, js_sprite_set_scale), + JS_CGETSET_DEF("skew", js_sprite_get_skew, js_sprite_set_skew), + JS_CGETSET_DEF("layer", js_sprite_get_layer, js_sprite_set_layer), + JS_CGETSET_DEF("color", js_sprite_get_color, js_sprite_set_color), + JS_CGETSET_DEF("center", js_sprite_get_center, js_sprite_set_center), + JS_CGETSET_DEF("rotation", js_sprite_get_rotation, js_sprite_set_rotation), }; #define JSSTATIC(NAME, PARENT) \ diff --git a/source/prosperon.c b/source/prosperon.c index 58d2feb2..4c190076 100644 --- a/source/prosperon.c +++ b/source/prosperon.c @@ -591,6 +591,7 @@ void script_startup(prosperon_rt *prt) JS_AddIntrinsicJSON(js); JS_AddIntrinsicMapSet(js); JS_AddIntrinsicTypedArrays(js); + JS_AddIntrinsicWeakRef(js); JS_SetContextOpaque(js, prt); prt->context = js; diff --git a/source/qjs_qr.c b/source/qjs_qr.c index 59551a92..c3b4809a 100644 --- a/source/qjs_qr.c +++ b/source/qjs_qr.c @@ -125,57 +125,83 @@ static JSValue js_qr_encode(JSContext *js, JSValueConst this_val, int argc, JSVa return result; } +static uint8_t rgba_to_gray(uint8_t r, uint8_t g, uint8_t b) +{ + return (uint8_t)(( 299 * r + 587 * g + 114 * b + 500) / 1000); +} + // QR decode function (unchanged) static JSValue js_qr_decode(JSContext *js, JSValueConst this_val, int argc, JSValueConst *argv) { - size_t data_len; - uint8_t *data; - if (argc < 1 || !(data = JS_GetArrayBuffer(js, &data_len, argv[0]))) { - return JS_ThrowTypeError(js, "decode expects an ArrayBuffer"); - } - - struct quirc *qr = quirc_new(); - if (!qr) { - return JS_ThrowInternalError(js, "Failed to initialize QR decoder"); - } - - int width = (int)sqrt((double)data_len); - if (width * width != (int)data_len) { - quirc_destroy(qr); - return JS_ThrowTypeError(js, "ArrayBuffer must represent a square image"); - } - - if (quirc_resize(qr, width, width) < 0) { - quirc_destroy(qr); - return JS_ThrowInternalError(js, "Failed to resize QR decoder"); - } - - int h; - uint8_t *image = quirc_begin(qr, &width, &h); - memcpy(image, data, data_len); - quirc_end(qr); - - int count = quirc_count(qr); - JSValue result = JS_NewArray(js); - - for (int i = 0; i < count; i++) { - struct quirc_code code; - struct quirc_data qdata; - - quirc_extract(qr, i, &code); - if (quirc_decode(&code, &qdata) == QUIRC_SUCCESS) { - JSValue item = JS_NewStringLen(js, (const char *)qdata.payload, qdata.payload_len); - JS_SetPropertyUint32(js, result, i, item); - } - } - + size_t data_len; + uint8_t *src; + int w, h, pitch; + src = JS_GetArrayBuffer(js, &data_len, argv[0]); + + if (!src) + return JS_ThrowTypeError(js, "decode expects an ArrayBuffer"); + + JS_ToInt32(js, &w, argv[1]); + JS_ToInt32(js, &h, argv[2]); + JS_ToInt32(js, &pitch, argv[3]); + + if (w <= 0 || h <= 0) + return JS_ThrowInternalError(js, "Bad width or height: %dx%d", w, h); + + struct quirc *qr = quirc_new(); + if (!qr) + return JS_ThrowInternalError(js, "Failed to initialize QR decoder"); + + if (quirc_resize(qr, w, h) < 0) { quirc_destroy(qr); - return result; + return JS_ThrowInternalError(js, "quirc_resize failed"); + } + + uint8_t *dst = quirc_begin(qr, NULL, NULL); + + printf("decoding image size %dx%d, pitch %d and size %d\n", w, h, pitch, data_len); + + for (int y = 0; y < h; ++y) { + uint8_t *row = src + y * pitch; + uint8_t *out = dst + y * w; + for (int x = 0; x < w; ++x) { + uint8_t a = row[x*4 + 3]; /* alpha */ + if (a < 128) { /* mostly transparent */ + out[x] = 255; + } else { + uint8_t r = row[x*4+0]; + uint8_t g = row[x*4+1]; + uint8_t b = row[x*4+2]; + out[x] = rgba_to_gray(r, g, b); + } + } + } + + quirc_end(qr); + + 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) { + JSValue item = JS_NewStringLen(js, (const char *)qdata.payload, qdata.payload_len); + JS_SetPropertyUint32(js, result, i, item); + } + } + + quirc_destroy(qr); + return result; } // Exported functions for the module static const JSCFunctionListEntry js_qr_funcs[] = { JS_CFUNC_DEF("encode", 2, js_qr_encode), // Updated to expect 2 args - JS_CFUNC_DEF("decode", 1, js_qr_decode), + JS_CFUNC_DEF("decode", 4, js_qr_decode), }; // Helper to return the default export object diff --git a/source/sprite.c b/source/sprite.c index 808b3523..ef0dc961 100644 --- a/source/sprite.c +++ b/source/sprite.c @@ -15,18 +15,15 @@ void sprite_free(JSRuntime *rt, sprite *sprite) void sprite_apply(sprite *sp) { - float rot = sp->rotation; - HMM_Vec2 k = sp->skew; - HMM_Vec2 s = sp->scale; + /* -------- rotation + skew → 2×2 affine matrix -------- */ + float rot = sp->rotation; + HMM_Vec2 k = sp->skew; - float c = cosf(rot), si = sinf(rot); + float c = cosf(rot), s = sinf(rot); - sp->affine.Columns[0] = (HMM_Vec2){ - .x = (c + k.x * si) * s.x, - .y = (k.y * c + si) * s.x - }; - sp->affine.Columns[1] = (HMM_Vec2){ - .x = (-si + k.x * c) * s.y, - .y = (-k.y * si + c) * s.y - }; + sp->affine.Columns[0] = (HMM_Vec2){ .x = c + k.x * s, + .y = k.y * c + s }; + sp->affine.Columns[1] = (HMM_Vec2){ .x = -s + k.x * c, + .y = -k.y * s + c }; } + diff --git a/source/sprite.h b/source/sprite.h index 31d1f8e7..a36f9d00 100644 --- a/source/sprite.h +++ b/source/sprite.h @@ -12,8 +12,7 @@ struct sprite{ HMM_Vec2 scale; float rotation; HMM_Mat2 affine; - JSValue image; // the actual texture resource - perhaps SDL_GPUTexture, or SDL_Texture, or something else ... - rect uv; // pixel coordinates of the sprite on its image + JSValue image; // the JS image int layer; // layer this sprite draws onto HMM_Vec4 color; // color in 0-1f }; diff --git a/tests/bunnymark.js b/tests/bunnymark.js index 646fd6b2..2c60c7ee 100644 --- a/tests/bunnymark.js +++ b/tests/bunnymark.js @@ -53,16 +53,13 @@ var bunny_count = 2000 for (var i = 0; i < bunny_count; i++) { var pct = i/bunny_count - var sp = sprite.create(bunny, [Math.random()*dim.x*pct, Math.random()*dim.y*pct], center) + var hue = 270 * i / bunny_count + var sp = sprite.create(bunny, { + pos: [Math.random()*dim.x*pct, Math.random()*dim.y*pct], + center, + color: hsl_to_rgb(hue, 0.5, 0.5) + }) sp.dir = [Math.random()*vel, Math.random()*vel] - var hue = 270 * i / bunny_count - sp.color = hsl_to_rgb(hue, 0.5, 0.5) -} - -function movendraw(sp) -{ - sp.move([sp.dir[0]*dt, sp.dir[1]*dt]) -// sp.draw() } var dt = 0 @@ -76,11 +73,15 @@ function loop() render.clear([22/255,120/255,194/255,255/255]) render.camera(camera) -// sprite.forEach(movendraw) + sprite.forEach(x => x.move(x.dir.scale(dt))) var queue = sprite.queue() - for (var q of queue) + //console.log(json.encode(queue)) + + for (var q of queue) { + if (!q.image) continue render.geometry(q.image.texture, q.mesh) + } render.present() dt = os.now() - now diff --git a/tests/qr_drag.js b/tests/qr_drag.js new file mode 100644 index 00000000..af682ef1 --- /dev/null +++ b/tests/qr_drag.js @@ -0,0 +1,76 @@ +// bunnymark +var render = use('render') +var os = use('os') +var dim = [500,500] +render.initialize({ + width:dim.x, + height:dim.y, + resolution_x:dim.x, + resolution_y:dim.y, + mode:"letterboxed", + refresh: 60, +}) + +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], +} + +var draw = use('draw2d') +var sprite = use('lcdsprite') +var graphics = use('graphics') + +var dt = 0 +var img = undefined + +var ioguy = { + __ACTORDATA__: { id: os.ioactor() } +} + +var io = use('io') +io.mount('/') + +var qr = use('qr') + +$_.send(ioguy, { + type: "subscribe", + actor: $_ +}) + +$_.receiver(e => { + if (e.type === 'quit') + os.exit() + + switch(e.type) { + case "drop_file": + console.log(`got ${e.data} dropped`) + img = e.data + var image = graphics.texture(e.data) + console.log(json.encode(qr.decode(image.surface.pixels(), image.surface.width(), image.surface.height(), image.surface.pitch()))) + } +}) + +function loop() +{ + var now = os.now() + 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}) + + render.present() + + dt = os.now() - now + var delay = (1/60) - dt + $_.delay(loop, delay) +} + +loop()