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

This commit is contained in:
2025-05-09 12:57:54 -05:00
parent 6bc04830d3
commit ff2ee3d6db
11 changed files with 473 additions and 126 deletions

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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

View File

@@ -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[] = {

View File

@@ -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;
}

View File

@@ -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
View 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 wont matter to anim speed */
$_.delay(loop, Math.max(0, (1/60) - (os.now()-now)));
}
loop();

View File

@@ -49,7 +49,7 @@ function hsl_to_rgb(h, s, l) {
return [r + m, g + m, b + m, 1] // 01 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

View File

@@ -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

Binary file not shown.