Fix sprite animation crash; move text/image to render

This commit is contained in:
John Alanbrook 2024-03-26 07:53:36 -05:00
parent b17e5d3917
commit 459ef00330
14 changed files with 483 additions and 163 deletions

View file

@ -315,37 +315,6 @@ time.text = function(num, fmt, zone)
return fmt; return fmt;
} }
var json = {};
json.encode = function(value, space, replacer, whitelist)
{
return JSON.stringify(value, space, replacer);
}
json.decode = function(text, reviver)
{
if (!text) return undefined;
return JSON.parse(text,reviver);
}
json.readout = function(obj)
{
var j = {};
for (var k in obj)
if (typeof obj[k] === 'function')
j[k] = 'function ' + obj[k].toString();
else
j[k] = obj[k];
return json.encode(j);
}
json.doc = {
doc: "json implementation.",
encode: "Encode a value to json.",
decode: "Decode a json string to a value.",
readout: "Encode an object fully, including function definitions."
};
Object.methods = function(o) Object.methods = function(o)
{ {
var m = []; var m = [];

View file

@ -5,7 +5,6 @@ this.world2view = function(pos) { return render.world2view(pos); };
this.realzoom = function() { return render.get_zoom(); }; this.realzoom = function() { return render.get_zoom(); };
this.right = function() { return this.pos.x + (window.rendersize.x/2); } this.right = function() { return this.pos.x + (window.rendersize.x/2); }
this.left = function() { return this.pos.x - (window.rendersize.x/2); } this.left = function() { return this.pos.x - (window.rendersize.x/2); }
this.zoom = 1; this.zoom = 1;

View file

@ -89,11 +89,14 @@ Object.mixin(os.sprite(true), {
anim:{}, anim:{},
playing: 0, playing: 0,
play(str) { play(str) {
console.trace();
this.del_anim?.();
var sp = this; var sp = this;
this.del_anim = function() { this.del_anim = function() {
sp = undefined; sp = undefined;
advance = undefined; advance = undefined;
this.del_anim = undefined; this.del_anim = undefined;
this.anim_done = undefined;
stop(); stop();
} }
str ??= 0; str ??= 0;
@ -101,17 +104,19 @@ Object.mixin(os.sprite(true), {
if (!playing) return; if (!playing) return;
var f = 0; var f = 0;
var stop; var stop;
sp.path = playing.path;
function advance() { function advance() {
if (!sp) this.del_anim(); if (!sp) this.del_anim();
if (!sp.gameobject) return; if (!sp.gameobject) return;
sp.path = playing.path; //sp.path = playing.path;
sp.frame = playing.frames[f].rect; sp.frame = playing.frames[f].rect;
f = (f+1)%playing.frames.length; f = (f+1)%playing.frames.length;
if (f === 0) sp.anim_done?.(); if (f === 0) sp.anim_done?.();
stop = sp.gameobject.delay(advance, playing.frames[f].time); stop = sp.gameobject.delay(advance, playing.frames[f].time);
} }
this.tex(game.texture(playing.path));
console.info(`playing anim: ${json.encode(playing)}`);
advance(); advance();
}, },
stop() { stop() {
@ -119,11 +124,13 @@ Object.mixin(os.sprite(true), {
}, },
set path(p) { set path(p) {
p = Resources.find_image(p); p = Resources.find_image(p);
if (!p) return; if (!p) {
console.warn(`Could not find image ${p}.`);
return;
}
if (p === this.path) return; if (p === this.path) return;
this._p = p; this._p = p;
this.del_anime?.(); this.del_anim?.();
this.tex(game.texture(p));
this.texture = game.texture(p); this.texture = game.texture(p);
this.tex(this.texture); this.tex(this.texture);
@ -131,6 +138,8 @@ Object.mixin(os.sprite(true), {
if (!anim) return; if (!anim) return;
this.anim = anim; this.anim = anim;
this.play(); this.play();
this.pos = this.dimensions().scale(this.anchor);
}, },
get path() { get path() {
return this._p; return this._p;
@ -146,7 +155,7 @@ Object.mixin(os.sprite(true), {
this.scale = this.scale.scale(x); this.scale = this.scale.scale(x);
this.pos = this.pos.scale(x); this.pos = this.pos.scale(x);
}, },
anchor:[0,0],
sync() { }, sync() { },
pick() { return this; }, pick() { return this; },
boundingbox() { boundingbox() {
@ -184,7 +193,7 @@ sprite.doc = {
pos: "The offset position of the sprite, relative to its entity." pos: "The offset position of the sprite, relative to its entity."
}; };
sprite.anchor = function(anch) { sprite.setanchor = function(anch) {
var off = [0,0]; var off = [0,0];
switch(anch) { switch(anch) {
case "ll": break; case "ll": break;
@ -197,19 +206,20 @@ sprite.anchor = function(anch) {
case "um": off = [-0.5,-1]; break; case "um": off = [-0.5,-1]; break;
case "ur": off = [-1,-1]; break; case "ur": off = [-1,-1]; break;
} }
this.anchor = off;
this.pos = this.dimensions().scale(off); this.pos = this.dimensions().scale(off);
} }
sprite.inputs = {}; sprite.inputs = {};
sprite.inputs.kp9 = function() { this.anchor("ll"); } sprite.inputs.kp9 = function() { this.setanchor("ll"); }
sprite.inputs.kp8 = function() { this.anchor("lm"); } sprite.inputs.kp8 = function() { this.setanchor("lm"); }
sprite.inputs.kp7 = function() { this.anchor("lr"); } sprite.inputs.kp7 = function() { this.setanchor("lr"); }
sprite.inputs.kp6 = function() { this.anchor("ml"); } sprite.inputs.kp6 = function() { this.setanchor("ml"); }
sprite.inputs.kp5 = function() { this.anchor("mm"); } sprite.inputs.kp5 = function() { this.setanchor("mm"); }
sprite.inputs.kp4 = function() { this.anchor("mr"); } sprite.inputs.kp4 = function() { this.setanchor("mr"); }
sprite.inputs.kp3 = function() { this.anchor("ur"); } sprite.inputs.kp3 = function() { this.setanchor("ur"); }
sprite.inputs.kp2 = function() { this.anchor("um"); } sprite.inputs.kp2 = function() { this.setanchor("um"); }
sprite.inputs.kp1 = function() { this.anchor("ul"); } sprite.inputs.kp1 = function() { this.setanchor("ul"); }
Object.seal(sprite); Object.seal(sprite);
@ -219,14 +229,23 @@ Object.seal(sprite);
time: miliseconds to hold the frame for time: miliseconds to hold the frame for
loop: true if it should be looped loop: true if it should be looped
*/ */
var animcache = {};
var SpriteAnim = { var SpriteAnim = {
make(path) { make(path) {
if (path.ext() === 'gif') if (animcache[path]) return animcache[path];
return SpriteAnim.gif(path); var anim;
if (io.exists(path.set_ext(".json")))
anim = SpriteAnim.aseprite(path.set_ext(".json"));
else if (path.ext() === 'gif')
anim = SpriteAnim.gif(path);
else if (path.ext() === 'ase') else if (path.ext() === 'ase')
return SpriteAnim.aseprite(path); anim = SpriteAnim.aseprite(path);
else else
return undefined; return undefined;
animcache[path] = anim;
console.spam(`Created animation like this:\n${json.encode(animcache[path])}`);
return animcache[path];
}, },
gif(path) { gif(path) {
console.info(`making an anim from ${path}`); console.info(`making an anim from ${path}`);
@ -250,7 +269,6 @@ var SpriteAnim = {
anim.frames.push(frame); anim.frames.push(frame);
} }
var times = tex.delays; var times = tex.delays;
console.info(`times are ${times}, num ${times.length}`);
for (var i = 0; i < frames; i++) for (var i = 0; i < frames; i++)
anim.frames[i].time = times[i]/1000; anim.frames[i].time = times[i]/1000;
anim.loop = true; anim.loop = true;
@ -278,38 +296,37 @@ var SpriteAnim = {
}, },
aseprite(path) { aseprite(path) {
function aseframeset2anim(frameset, meta) { function aseframeset2anim(frameset, meta) {
var anim = {}; var anim = {};
anim.frames = []; anim.frames = [];
anim.path = meta.image; anim.path = path.dir() + "/" + meta.image;
var dim = meta.size; var dim = meta.size;
var ase_make_frame = function(ase_frame,i) { var ase_make_frame = function(ase_frame) {
var f = ase_frame.frame; var f = ase_frame.frame;
var frame = {}; var frame = {};
frame.rect = { frame.rect = {
x: f.x/dim.w, x: f.x/dim.w,
w: f.w/dim.w, w: f.w/dim.w,
y: f.y/dim.h, y: f.y/dim.h,
h: f.h/dim.h h: f.h/dim.h
};
frame.time = ase_frame.duration / 1000;
anim.frames.push(frame);
}; };
frame.time = ase_frame.duration / 1000;
anim.frames.push(frame); frameset.forEach(ase_make_frame);
anim.dim = frameset[0].sourceSize;
anim.loop = true;
return anim;
}; };
frameset.forEach(ase_make_frame); var data = json.decode(io.slurp(path));
anim.dim = [frameset[0].sourceSize.x, frameset[0].sourceSize.y];
anim.loop = true;
return anim;
};
var json = io.slurp(path);
json = JSON.parse(json);
var anims = {}; var anims = {};
var frames = Array.isArray(json.frames) ? json.frames : Object.values(json.frames); var frames = Array.isArray(data.frames) ? data.frames : Object.values(data.frames);
var f = 0; var f = 0;
for (var tag of json.meta.frameTags) { for (var tag of data.meta.frameTags) {
anims[tag.name] = aseframeset2anim(frames.slice(tag.from, tag.to+1), json.meta); anims[tag.name] = aseframeset2anim(frames.slice(tag.from, tag.to+1), data.meta);
anims[f] = anims[tag.name]; anims[f] = anims[tag.name];
f++; f++;
} }

View file

@ -27,19 +27,19 @@ debug.draw = function() {
if (this.draw_names) if (this.draw_names)
game.all_objects(function(x) { game.all_objects(function(x) {
GUI.text(x, window.world2screen(x.pos).add([0,32]), 1, Color.debug.names); render.text(x, window.world2screen(x.pos).add([0,32]), 1, Color.debug.names);
}); });
if (debug.gif.rec) { if (debug.gif.rec) {
GUI.text("REC", [0,40], 1); render.text("REC", [0,40], 1);
GUI.text(time.timecode(time.timenow() - debug.gif.start_time, debug.gif.fps), [0,30], 1); render.text(time.timecode(time.timenow() - debug.gif.start_time, debug.gif.fps), [0,30], 1);
} }
return; return;
if (sim.paused()) GUI.text("PAUSED", [0,0],1); if (sim.paused()) render.text("PAUSED", [0,0],1);
GUI.text(sim.playing() ? "PLAYING" render.text(sim.playing() ? "PLAYING"
: sim.stepping() ? : sim.stepping() ?
"STEP" : "STEP" :
sim.paused() ? sim.paused() ?

View file

@ -343,7 +343,7 @@ var editor = {
root = root ? root + "." : root; root = root ? root + "." : root;
Object.entries(obj.objects).forEach(function(x) { Object.entries(obj.objects).forEach(function(x) {
var p = root + x[0]; var p = root + x[0];
GUI.text(p, x[1].screenpos(), 1, editor.color_depths[depth]); render.text(p, x[1].screenpos(), 1, editor.color_depths[depth]);
editor.draw_objects_names(x[1], p, depth+1); editor.draw_objects_names(x[1], p, depth+1);
}); });
}, },
@ -393,13 +393,13 @@ var editor = {
gui() { gui() {
/* Clean out killed objects */ /* Clean out killed objects */
this.selectlist = this.selectlist.filter(function(x) { return x.alive; }); this.selectlist = this.selectlist.filter(function(x) { return x.alive; });
GUI.text([0,0], window.world2screen([0,0])); render.text([0,0], window.world2screen([0,0]));
GUI.text("WORKING LAYER: " + this.working_layer, [0,520]); render.text("WORKING LAYER: " + this.working_layer, [0,520]);
GUI.text("MODE: " + this.edit_mode, [0,500]); render.text("MODE: " + this.edit_mode, [0,500]);
if (this.comp_info && this.sel_comp) if (this.comp_info && this.sel_comp)
GUI.text(Input.print_pawn_kbm(this.sel_comp,false), [100,700],1); render.text(Input.print_pawn_kbm(this.sel_comp,false), [100,700],1);
render.cross(editor.edit_level.screenpos(),3,Color.blue); render.cross(editor.edit_level.screenpos(),3,Color.blue);
@ -434,24 +434,24 @@ var editor = {
depth = i; depth = i;
var lvlstr = x.namestr(); var lvlstr = x.namestr();
if (i === lvlchain.length-1) lvlstr += "[this]"; if (i === lvlchain.length-1) lvlstr += "[this]";
GUI.text(lvlstr, [0, ypos], 1, editor.color_depths[depth]); render.text(lvlstr, [0, ypos], 1, editor.color_depths[depth]);
GUI.text("^^^^^^", [0,ypos+=5],1); render.text("^^^^^^", [0,ypos+=5],1);
ypos += 15; ypos += 15;
}); });
depth++; depth++;
GUI.text("$$$$$$", [0,ypos],1,editor.color_depths[depth]); render.text("$$$$$$", [0,ypos],1,editor.color_depths[depth]);
this.selectlist.forEach(function(x) { this.selectlist.forEach(function(x) {
GUI.text(x.urstr(), x.screenpos().add([0, 32]), 1, Color.editor.ur); render.text(x.urstr(), x.screenpos().add([0, 32]), 1, Color.editor.ur);
GUI.text(x.worldpos().map(function(x) { return Math.round(x); }), x.screenpos(), 1, Color.white); render.text(x.worldpos().map(function(x) { return Math.round(x); }), x.screenpos(), 1, Color.white);
render.cross(x.screenpos(), 10, Color.blue); render.cross(x.screenpos(), 10, Color.blue);
}); });
Object.entries(thiso.objects).forEach(function(x) { Object.entries(thiso.objects).forEach(function(x) {
var p = x[1].namestr(); var p = x[1].namestr();
GUI.text(p, x[1].screenpos().add([0,16]),1,editor.color_depths[depth]); render.text(p, x[1].screenpos().add([0,16]),1,editor.color_depths[depth]);
render.circle(x[1].screenpos(),10,Color.blue.alpha(0.3)); render.circle(x[1].screenpos(),10,Color.blue.alpha(0.3));
}); });
@ -459,18 +459,18 @@ var editor = {
if (mg) { if (mg) {
var p = mg.path_from(thiso); var p = mg.path_from(thiso);
GUI.text(p, Mouse.screenpos(),1,Color.teal); render.text(p, Mouse.screenpos(),1,Color.teal);
} }
if (this.rotlist.length === 1) if (this.rotlist.length === 1)
GUI.text(Math.trunc(this.rotlist[0].obj.angle), Mouse.screenpos(), 1, Color.teal); render.text(Math.trunc(this.rotlist[0].obj.angle), Mouse.screenpos(), 1, Color.teal);
if (this.selectlist.length === 1) { if (this.selectlist.length === 1) {
var i = 1; var i = 1;
for (var key in this.selectlist[0].components) { for (var key in this.selectlist[0].components) {
var selected = this.sel_comp === this.selectlist[0].components[key]; var selected = this.sel_comp === this.selectlist[0].components[key];
var str = (selected ? ">" : " ") + key + " [" + this.selectlist[0].components[key].toString() + "]"; var str = (selected ? ">" : " ") + key + " [" + this.selectlist[0].components[key].toString() + "]";
GUI.text(str, this.selectlist[0].screenpos().add([0,-16*(i++)])); render.text(str, this.selectlist[0].screenpos().add([0,-16*(i++)]));
} }
if (this.sel_comp) { if (this.sel_comp) {
@ -480,7 +480,7 @@ var editor = {
editor.edit_level.objects.forEach(function(obj) { editor.edit_level.objects.forEach(function(obj) {
if (!obj._ed.selectable) if (!obj._ed.selectable)
GUI.text("lock", obj,screenpos()); render.text("lock", obj,screenpos());
}); });
render.grid(1, editor.grid_size, Color.Editor.grid.alpha(0.3)); render.grid(1, editor.grid_size, Color.Editor.grid.alpha(0.3));
@ -494,12 +494,12 @@ var editor = {
if (h_step === 0) h_step = editor.grid_size; if (h_step === 0) h_step = editor.grid_size;
while(startgrid[0] <= endgrid[0]) { while(startgrid[0] <= endgrid[0]) {
GUI.text(startgrid[0], [window.world2screen([startgrid[0], 0])[0],0]); render.text(startgrid[0], [window.world2screen([startgrid[0], 0])[0],0]);
startgrid[0] += w_step; startgrid[0] += w_step;
} }
while(startgrid[1] <= endgrid[1]) { while(startgrid[1] <= endgrid[1]) {
GUI.text(startgrid[1], [0, window.world2screen([0, startgrid[1]])[1]]); render.text(startgrid[1], [0, window.world2screen([0, startgrid[1]])[1]]);
startgrid[1] += h_step; startgrid[1] += h_step;
} }

View file

@ -1,5 +1,37 @@
"use math"; "use math";
globalThis.json = {};
json.encode = function(value, replacer, space, whitelist)
{
space ??= 1;
return JSON.stringify(value, replacer, space);
}
json.decode = function(text, reviver)
{
if (!text) return undefined;
return JSON.parse(text,reviver);
}
json.readout = function(obj)
{
var j = {};
for (var k in obj)
if (typeof obj[k] === 'function')
j[k] = 'function ' + obj[k].toString();
else
j[k] = obj[k];
return json.encode(j);
}
json.doc = {
doc: "json implementation.",
encode: "Encode a value to json.",
decode: "Decode a json string to a value.",
readout: "Encode an object fully, including function definitions."
};
globalThis.Resources = {}; globalThis.Resources = {};
Resources.scripts = ["jsoc", "jsc", "jso", "js"]; Resources.scripts = ["jsoc", "jsc", "jso", "js"];
Resources.images = ["png", "gif", "jpg", "jpeg"]; Resources.images = ["png", "gif", "jpg", "jpeg"];
@ -79,6 +111,7 @@ Object.assign(console, {
console.stdout_lvl = 1; console.stdout_lvl = 1;
console.log = console.say; console.log = console.say;
console.trace = console.stack;
var say = console.say; var say = console.say;
var print = console.print; var print = console.print;
@ -217,7 +250,6 @@ function process()
render.pass(); render.pass();
prosperon.gui(); prosperon.gui();
render.flush_hud(); render.flush_hud();
render.end_pass(); render.end_pass();
render.commit(); render.commit();
} }
@ -242,7 +274,14 @@ game.doc.camera = "Current camera.";
game.texture = function(path) game.texture = function(path)
{ {
game.texture.cache[path] ??= os.make_texture(path); if (game.texture.cache[path]) return game.texture.cache[path];
if (!io.exists(path)) {
console.warn(`Missing texture: ${path}`);
game.texture.cache[path] = game.texture("icons/no_tex.gif");
} else
game.texture.cache[path] ??= os.make_texture(path);
return game.texture.cache[path]; return game.texture.cache[path];
} }
game.texture.cache = {}; game.texture.cache = {};

View file

@ -184,7 +184,7 @@ var gameobject = {
that.timers.remove(stop); that.timers.remove(stop);
execute = undefined; execute = undefined;
stop = undefined; stop = undefined;
rm(); rm?.();
rm = undefined; rm = undefined;
update = undefined; update = undefined;
} }
@ -570,7 +570,6 @@ var gameobject = {
this.objects = undefined; this.objects = undefined;
if (typeof this.stop === 'function') this.stop(); if (typeof this.stop === 'function') this.stop();
if (typeof this.die === 'function') this.die();
}, },
up() { return [0, 1].rotate(this.angle); }, up() { return [0, 1].rotate(this.angle); },

View file

@ -5,38 +5,6 @@
gui.scissor_win = function() { gui.scissor(0,0,window.width,window.height); } gui.scissor_win = function() { gui.scissor(0,0,window.width,window.height); }
var GUI = { var GUI = {
text(str, pos, size, color, wrap, anchor, cursor) {
size ??= 1;
color ??= Color.white;
wrap ??= -1;
anchor ??= [0,1];
cursor ??= -1;
var bb = render.text_size(str, size, wrap);
var w = bb.r*2;
var h = bb.t*2;
//gui.text draws with an anchor on top left corner
var p = pos.slice();
p.x -= w * anchor.x;
bb.r += (w*anchor.x);
bb.l += (w*anchor.x);
p.y += h * (1 - anchor.y);
bb.t += h*(1-anchor.y);
bb.b += h*(1-anchor.y);
gui.text(str, p, size, color, wrap, cursor);
return bb;
},
image(path,pos,color) {
color ??= Color.black;
var wh = texture.dimensions(64,path);
gui_img(path,pos, [1.0,1.0], 0.0, false, [0.0,0.0], Color.white);
return bbox.fromcwh([0,0], wh);
},
newmg(img) { newmg(img) {
var def = { var def = {
path: "", path: "",

View file

@ -89,7 +89,7 @@ render.arrow = function(start, end, color, wingspan, wingangle) {
render.coordinate = function(pos, size, color) { render.coordinate = function(pos, size, color) {
color ??= Color.white; color ??= Color.white;
GUI.text(JSON.stringify(pos.map(p=>Math.round(p))), pos, size, color); render.text(JSON.stringify(pos.map(p=>Math.round(p))), pos, size, color);
render.point(pos, 2, color); render.point(pos, 2, color);
} }
@ -110,6 +110,39 @@ render.box = function(pos, wh, color) {
render.rectangle(lower,upper,color); render.rectangle(lower,upper,color);
}; };
render.text = function(str, pos, size, color, wrap, anchor, cursor) {
size ??= 1;
color ??= Color.white;
wrap ??= -1;
anchor ??= [0,1];
cursor ??= -1;
var bb = render.text_size(str, size, wrap);
var w = bb.r*2;
var h = bb.t*2;
//gui.text draws with an anchor on top left corner
var p = pos.slice();
p.x -= w * anchor.x;
bb.r += (w*anchor.x);
bb.l += (w*anchor.x);
p.y += h * (1 - anchor.y);
bb.t += h*(1-anchor.y);
bb.b += h*(1-anchor.y);
gui.text(str, p, size, color, wrap, cursor);
return bb;
};
render.image = function(tex, pos, rotation, color) {
color ??= Color.black;
rotation ??= 0;
// var wh = texture.dimensions(64,path);
gui.img(tex,pos, [1.0,1.0], 0.0, false, [0.0,0.0], Color.white);
// return bbox.fromcwh([0,0], wh);
}
render.doc = "Draw shapes in screen space."; render.doc = "Draw shapes in screen space.";
render.circle.doc = "Draw a circle at pos, with a given radius and color."; render.circle.doc = "Draw a circle at pos, with a given radius and color.";
render.cross.doc = "Draw a cross centered at pos, with arm length size."; render.cross.doc = "Draw a cross centered at pos, with arm length size.";

299
source/engine/gif_load.h Normal file
View file

@ -0,0 +1,299 @@
#ifndef GIF_LOAD_H
#define GIF_LOAD_H
/** gif_load: A slim, fast and header-only GIF loader written in C.
Original author: hidefromkgb (hidefromkgb@gmail.com)
_________________________________________________________________________
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
_________________________________________________________________________
**/
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h> /** imports uint8_t, uint16_t and uint32_t **/
#ifndef GIF_MGET
#include <stdlib.h>
#define GIF_MGET(m,s,a,c) m = (uint8_t*)realloc((c)? 0 : m, (c)? s : 0UL);
#endif
#ifndef GIF_BIGE
#define GIF_BIGE 0
#endif
#ifndef GIF_EXTR
#define GIF_EXTR static
#endif
#define _GIF_SWAP(h) ((GIF_BIGE)? ((uint16_t)(h << 8) | (h >> 8)) : h)
#pragma pack(push, 1)
struct GIF_WHDR { /** ======== frame writer info: ======== **/
long xdim, ydim, clrs, /** global dimensions, palette size **/
bkgd, tran, /** background index, transparent index **/
intr, mode, /** interlace flag, frame blending mode **/
frxd, fryd, frxo, fryo, /** current frame dimensions and offset **/
time, ifrm, nfrm; /** delay, frame number, frame count **/
uint8_t *bptr; /** frame pixel indices or metadata **/
struct { /** [==== GIF RGB palette element: ====] **/
uint8_t R, G, B; /** [color values - red, green, blue ] **/
} *cpal; /** current palette **/
};
#pragma pack(pop)
enum {GIF_NONE = 0, GIF_CURR = 1, GIF_BKGD = 2, GIF_PREV = 3};
/** [ internal function, do not use ] **/
static long _GIF_SkipChunk(uint8_t **buff, long size) {
long skip;
for (skip = 2, ++size, ++(*buff); ((size -= skip) > 0) && (skip > 1);
*buff += (skip = 1 + **buff));
return size;
}
/** [ internal function, do not use ] **/
static long _GIF_LoadHeader(unsigned gflg, uint8_t **buff, void **rpal,
unsigned fflg, long *size, long flen) {
if (flen && (!(*buff += flen) || ((*size -= flen) <= 0)))
return -2; /** v--[ 0x80: "palette is present" flag ]--, **/
if (flen && (fflg & 0x80)) { /** local palette has priority | **/
*rpal = *buff; /** [ 3L: 3 uint8_t color channels ]--, | **/
*buff += (flen = 2 << (fflg & 7)) * 3L; /** <--| | **/
return ((*size -= flen * 3L) > 0)? flen : -1; /** <--' | **/
} /** no local palette found, checking for the global one | **/
return (gflg & 0x80)? (2 << (gflg & 7)) : 0; /** <-----' **/
}
/** [ internal function, do not use ] **/
static long _GIF_LoadFrame(uint8_t **buff, long *size,
uint8_t *bptr, uint8_t *blen) {
typedef uint16_t GIF_H;
const long GIF_HLEN = sizeof(GIF_H), /** to rid the scope of sizeof **/
GIF_CLEN = 1 << 12; /** code table length: 4096 items **/
GIF_H accu, mask; /** bit accumulator / bit mask **/
long ctbl, iter, /** last code table index / index string iterator **/
prev, curr, /** codes from the stream: previous / current **/
ctsz, ccsz, /** code table bit sizes: min LZW / current **/
bseq, bszc; /** counters: block sequence / bit size **/
uint32_t *code = (uint32_t*)bptr - GIF_CLEN; /** code table pointer **/
/** preparing initial values **/
if ((--(*size) <= GIF_HLEN) || !*++(*buff))
return -4; /** unexpected end of the stream: insufficient size **/
mask = (GIF_H)((1 << (ccsz = (ctsz = *(*buff - 1)) + 1)) - 1);
if ((ctsz < 2) || (ctsz > 8))
return -3; /** min LZW size is out of its nominal [2; 8] bounds **/
if ((ctbl = (1L << ctsz)) != (mask & _GIF_SWAP(*(GIF_H*)(*buff + 1))))
return -2; /** initial code is not equal to min LZW size **/
for (curr = ++ctbl; curr; code[--curr] = 0); /** actual color codes **/
/** getting codes from stream (--size makes up for end-of-stream mark) **/
for (--(*size), bszc = -ccsz, prev = curr = 0;
((*size -= (bseq = *(*buff)++) + 1) >= 0) && bseq; *buff += bseq)
for (; bseq > 0; bseq -= GIF_HLEN, *buff += GIF_HLEN)
for (accu = (GIF_H)(_GIF_SWAP(*(GIF_H*)*buff)
& ((bseq < GIF_HLEN)? ((1U << (8 * bseq)) - 1U) : ~0U)),
curr |= accu << (ccsz + bszc), accu = (GIF_H)(accu >> -bszc),
bszc += 8 * ((bseq < GIF_HLEN)? bseq : GIF_HLEN);
bszc >= 0; bszc -= ccsz, prev = curr, curr = accu,
accu = (GIF_H)(accu >> ccsz))
if (((curr &= mask) & ~1L) == (1L << ctsz)) {
if (~(ctbl = curr + 1) & 1) /** end-of-data code (ED). **/
/** -1: no end-of-stream mark after ED; 1: decoded **/
return (*((*buff += bseq + 1) - 1))? -1 : 1;
mask = (GIF_H)((1 << (ccsz = ctsz + 1)) - 1);
} /** ^- table drop code (TD). TD = 1 << ctsz, ED = TD + 1 **/
else { /** single-pixel (SP) or multi-pixel (MP) code. **/
if (ctbl < GIF_CLEN) { /** is the code table full? **/
if ((ctbl == mask) && (ctbl < GIF_CLEN - 1)) {
mask = (GIF_H)(mask + mask + 1);
ccsz++; /** yes; extending **/
} /** prev = TD? => curr < ctbl = prev **/
code[ctbl] = (uint32_t)prev + (code[prev] & 0xFFF000);
} /** appending SP / MP decoded pixels to the frame **/
prev = (long)code[iter = (ctbl > curr)? curr : prev];
if ((bptr += (prev = (prev >> 12) & 0xFFF)) > blen)
continue; /** skipping pixels above frame capacity **/
for (prev++; (iter &= 0xFFF) >> ctsz;
*bptr-- = (uint8_t)((iter = (long)code[iter]) >> 24));
(bptr += prev)[-prev] = (uint8_t)iter;
if (ctbl < GIF_CLEN) { /** appending the code table **/
if (ctbl == curr)
*bptr++ = (uint8_t)iter;
else if (ctbl < curr)
return -5; /** wrong code in the stream **/
code[ctbl++] += ((uint32_t)iter << 24) + 0x1000;
}
} /** 0: no ED before end-of-stream mark; -4: see above **/
return (++(*size) >= 0)? 0 : -4; /** ^- N.B.: 0 error is recoverable **/
}
/** _________________________________________________________________________
The main loading function. Returns the total number of frames if the data
includes proper GIF ending, and otherwise it returns the number of frames
loaded per current call, multiplied by -1. So, the data may be incomplete
and in this case the function can be called again when more data arrives,
just remember to keep SKIP up to date.
_________________________________________________________________________
DATA: raw data chunk, may be partial
SIZE: size of the data chunk that`s currently present
GWFR: frame writer function, MANDATORY
EAMF: metadata reader function, set to 0 if not needed
ANIM: implementation-specific data (e.g. a structure or a pointer to it)
SKIP: number of frames to skip before resuming
**/
GIF_EXTR long GIF_Load(void *data, long size,
void (*gwfr)(void*, struct GIF_WHDR*),
void (*eamf)(void*, struct GIF_WHDR*),
void *anim, long skip) {
const long GIF_BLEN = (1 << 12) * sizeof(uint32_t);
const uint8_t GIF_EHDM = 0x21, /** extension header mark **/
GIF_FHDM = 0x2C, /** frame header mark **/
GIF_EOFM = 0x3B, /** end-of-file mark **/
GIF_EGCM = 0xF9, /** extension: graphics control mark **/
GIF_EAMM = 0xFF; /** extension: app metadata mark **/
#pragma pack(push, 1)
struct GIF_GHDR { /** ========== GLOBAL GIF HEADER: ========== **/
uint8_t head[6]; /** 'GIF87a' / 'GIF89a' header signature **/
uint16_t xdim, ydim; /** total image width, total image height **/
uint8_t flgs; /** FLAGS:
GlobalPlt bit 7 1: global palette exists
0: local in each frame
ClrRes bit 6-4 bits/channel = ClrRes+1
[reserved] bit 3 0
PixelBits bit 2-0 |Plt| = 2 * 2^PixelBits
**/
uint8_t bkgd, aspr; /** background color index, aspect ratio **/
} *ghdr = (struct GIF_GHDR*)data;
struct GIF_FHDR { /** ======= GIF FRAME MASTER HEADER: ======= **/
uint16_t frxo, fryo; /** offset of this frame in a "full" image **/
uint16_t frxd, fryd; /** frame width, frame height **/
uint8_t flgs; /** FLAGS:
LocalPlt bit 7 1: local palette exists
0: global is used
Interlaced bit 6 1: interlaced frame
0: non-interlaced frame
Sorted bit 5 usually 0
[reserved] bit 4-3 [undefined]
PixelBits bit 2-0 |Plt| = 2 * 2^PixelBits
**/
} *fhdr;
struct GIF_EGCH { /** ==== [EXT] GRAPHICS CONTROL HEADER: ==== **/
uint8_t flgs; /** FLAGS:
[reserved] bit 7-5 [undefined]
BlendMode bit 4-2 000: not set; static GIF
001: leave result as is
010: restore background
011: restore previous
1--: [undefined]
UserInput bit 1 1: show frame till input
0: default; ~99% of GIFs
TransColor bit 0 1: got transparent color
0: frame is fully opaque
**/
uint16_t time; /** delay in GIF time units; 1 unit = 10 ms **/
uint8_t tran; /** transparent color index **/
} *egch = 0;
#pragma pack(pop)
struct GIF_WHDR wtmp, whdr = {0};
long desc, blen;
uint8_t *buff;
/** checking if the stream is not empty and has a 'GIF8[79]a' signature,
the data has sufficient size and frameskip value is non-negative **/
if (!ghdr || (size <= (long)sizeof(*ghdr)) || (*(buff = ghdr->head) != 71)
|| (buff[1] != 73) || (buff[2] != 70) || (buff[3] != 56) || (skip < 0)
|| ((buff[4] != 55) && (buff[4] != 57)) || (buff[5] != 97) || !gwfr)
return 0;
buff = (uint8_t*)(ghdr + 1) /** skipping the global header and palette **/
+ _GIF_LoadHeader(ghdr->flgs, 0, 0, 0, 0, 0L) * 3L;
if ((size -= buff - (uint8_t*)ghdr) <= 0)
return 0;
whdr.xdim = _GIF_SWAP(ghdr->xdim);
whdr.ydim = _GIF_SWAP(ghdr->ydim);
for (whdr.bptr = buff, whdr.bkgd = ghdr->bkgd, blen = --size;
(blen >= 0) && ((desc = *whdr.bptr++) != GIF_EOFM); /** sic: '>= 0' **/
blen = _GIF_SkipChunk(&whdr.bptr, blen) - 1) /** count all frames **/
if (desc == GIF_FHDM) {
fhdr = (struct GIF_FHDR*)whdr.bptr;
if (_GIF_LoadHeader(ghdr->flgs, &whdr.bptr, (void**)&whdr.cpal,
fhdr->flgs, &blen, sizeof(*fhdr)) <= 0)
break;
whdr.frxd = _GIF_SWAP(fhdr->frxd);
whdr.fryd = _GIF_SWAP(fhdr->fryd);
whdr.frxo = (whdr.frxd > whdr.frxo)? whdr.frxd : whdr.frxo;
whdr.fryo = (whdr.fryd > whdr.fryo)? whdr.fryd : whdr.fryo;
whdr.ifrm++;
}
blen = whdr.frxo * whdr.fryo * (long)sizeof(*whdr.bptr);
GIF_MGET(whdr.bptr, (unsigned long)(blen + GIF_BLEN + 2), anim, 1)
whdr.nfrm = (desc != GIF_EOFM)? -whdr.ifrm : whdr.ifrm;
for (whdr.bptr += GIF_BLEN, whdr.ifrm = -1; blen /** load all frames **/
&& (skip < ((whdr.nfrm < 0)? -whdr.nfrm : whdr.nfrm)) && (size >= 0);
size = (desc != GIF_EOFM)? ((desc != GIF_FHDM) || (skip > whdr.ifrm))?
_GIF_SkipChunk(&buff, size) - 1 : size - 1 : -1)
if ((desc = *buff++) == GIF_FHDM) { /** found a frame **/
whdr.intr = !!((fhdr = (struct GIF_FHDR*)buff)->flgs & 0x40);
*(void**)&whdr.cpal = (void*)(ghdr + 1); /** interlaced? -^ **/
whdr.clrs = _GIF_LoadHeader(ghdr->flgs, &buff, (void**)&whdr.cpal,
fhdr->flgs, &size, sizeof(*fhdr));
if ((skip <= ++whdr.ifrm) && ((whdr.clrs <= 0)
|| (_GIF_LoadFrame(&buff, &size,
whdr.bptr, whdr.bptr + blen) < 0)))
size = -(whdr.ifrm--) - 1; /** failed to load the frame **/
else if (skip <= whdr.ifrm) {
whdr.frxd = _GIF_SWAP(fhdr->frxd);
whdr.fryd = _GIF_SWAP(fhdr->fryd);
whdr.frxo = _GIF_SWAP(fhdr->frxo);
whdr.fryo = _GIF_SWAP(fhdr->fryo);
whdr.time = (egch)? _GIF_SWAP(egch->time) : 0;
whdr.tran = (egch && (egch->flgs & 0x01))? egch->tran : -1;
whdr.time = (egch && (egch->flgs & 0x02))? -whdr.time - 1
: whdr.time;
whdr.mode = (egch && !(egch->flgs & 0x10))?
(egch->flgs & 0x0C) >> 2 : GIF_NONE;
egch = 0;
wtmp = whdr;
gwfr(anim, &wtmp); /** passing the frame to the caller **/
}
}
else if (desc == GIF_EHDM) { /** found an extension **/
if (*buff == GIF_EGCM) /** graphics control ext. **/
egch = (struct GIF_EGCH*)(buff + 1 + 1);
else if ((*buff == GIF_EAMM) && eamf) { /** app metadata ext. **/
wtmp = whdr;
wtmp.bptr = buff + 1 + 1; /** just passing the raw chunk **/
eamf(anim, &wtmp);
}
}
whdr.bptr -= GIF_BLEN; /** for excess pixel codes ----v (here & above) **/
GIF_MGET(whdr.bptr, (unsigned long)(blen + GIF_BLEN + 2), anim, 0)
return (whdr.nfrm < 0)? (skip - whdr.ifrm - 1) : (whdr.ifrm + 1);
}
#undef _GIF_SWAP
#ifdef __cplusplus
}
#endif
#endif /** GIF_LOAD_H **/

View file

@ -899,13 +899,11 @@ JSC_CCALL(gui_text,
) )
JSC_CCALL(gui_img, JSC_CCALL(gui_img,
const char *img = JS_ToCString(js, argv[0]);
transform2d t; transform2d t;
t.pos = js2vec2(argv[1]); t.pos = js2vec2(argv[1]);
t.scale = js2vec2(argv[2]); t.scale = js2vec2(argv[2]);
t.angle = js2number(argv[3]); t.angle = js2number(argv[3]);
gui_draw_img(img, t, js2boolean(argv[4]), js2vec2(argv[5]), 1.0, js2color(argv[6])); gui_draw_img(js2texture(argv[0]), t, js2boolean(argv[4]), js2vec2(argv[5]), 1.0, js2color(argv[6]));
JS_FreeCString(js, img);
) )
JSC_SCALL(gui_font_set, font_set(str)) JSC_SCALL(gui_font_set, font_set(str))

View file

@ -111,9 +111,9 @@ void sprite_initialize() {
.layout = { .layout = {
.attrs = { .attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2, [0].format = SG_VERTEXFORMAT_FLOAT2,
[1].format = SG_VERTEXFORMAT_FLOAT2, [1].format = SG_VERTEXFORMAT_FLOAT2,
[2].format = SG_VERTEXFORMAT_UBYTE4N, [2].format = SG_VERTEXFORMAT_UBYTE4N,
[3].format = SG_VERTEXFORMAT_UBYTE4N}}, [3].format = SG_VERTEXFORMAT_UBYTE4N}},
.primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP, .primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP,
.label = "sprite pipeline", .label = "sprite pipeline",
.colors[0].blend = blend_trans, .colors[0].blend = blend_trans,
@ -134,10 +134,10 @@ void sprite_initialize() {
.layout = { .layout = {
.attrs = { .attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2, [0].format = SG_VERTEXFORMAT_FLOAT2,
[1].format = SG_VERTEXFORMAT_FLOAT2, [1].format = SG_VERTEXFORMAT_FLOAT2,
[2].format = SG_VERTEXFORMAT_USHORT4N, [2].format = SG_VERTEXFORMAT_USHORT4N,
[3].format = SG_VERTEXFORMAT_FLOAT2, [3].format = SG_VERTEXFORMAT_FLOAT2,
[4].format = SG_VERTEXFORMAT_UBYTE4N [4].format = SG_VERTEXFORMAT_UBYTE4N
}}, }},
.primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP, .primitive_type = SG_PRIMITIVETYPE_TRIANGLE_STRIP,
}); });
@ -212,10 +212,8 @@ void sprite_draw(struct sprite *sprite) {
tex_draw(sprite->tex, HMM_MulM3(m,sm), sprite->frame, sprite->color, sprite->drawmode, (HMM_Vec2){0,0}, sprite->scale, sprite->emissive, sprite->parallax); tex_draw(sprite->tex, HMM_MulM3(m,sm), sprite->frame, sprite->color, sprite->drawmode, (HMM_Vec2){0,0}, sprite->scale, sprite->emissive, sprite->parallax);
} }
void gui_draw_img(const char *img, transform2d t, int wrap, HMM_Vec2 wrapoffset, float wrapscale, struct rgba color) { void gui_draw_img(texture *tex, transform2d t, int wrap, HMM_Vec2 wrapoffset, float wrapscale, struct rgba color) {
return; //sg_apply_pipeline(pip_sprite);
sg_apply_pipeline(pip_sprite); //sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE_REF(hudproj));
sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE_REF(hudproj));
struct texture *tex = texture_from_file(img);
tex_draw(tex, transform2d2mat(t), ST_UNIT, color, wrap, wrapoffset, (HMM_Vec2){wrapscale,wrapscale}, (struct rgba){0,0,0,0}, 0); tex_draw(tex, transform2d2mat(t), ST_UNIT, color, wrap, wrapoffset, (HMM_Vec2){wrapscale,wrapscale}, (struct rgba){0,0,0,0}, 0);
} }

View file

@ -36,6 +36,6 @@ void sprite_draw(struct sprite *sprite);
void sprite_draw_all(); void sprite_draw_all();
void sprite_flush(); void sprite_flush();
void gui_draw_img(const char *img, transform2d t, int wrap, HMM_Vec2 wrapoffset, float wrapscale, struct rgba color); void gui_draw_img(texture *tex, transform2d t, int wrap, HMM_Vec2 wrapoffset, float wrapscale, struct rgba color);
#endif #endif

View file

@ -117,15 +117,16 @@ struct texture *texture_from_file(const char *path) {
if (data == NULL) if (data == NULL)
return NULL; return NULL;
tex->data = data;
unsigned int nw = next_pow2(tex->width); unsigned int nw = next_pow2(tex->width);
unsigned int nh = next_pow2(tex->height); unsigned int nh = next_pow2(tex->height);
tex->data = data;
int filter = SG_FILTER_NEAREST; int filter = SG_FILTER_NEAREST;
sg_image_data sg_img_data; sg_image_data sg_img_data;
sg_img_data.subimage[0][0] = (sg_range){.ptr = data, .size=tex->width*tex->height*4};
/*
int mips = mip_levels(tex->width, tex->height)+1; int mips = mip_levels(tex->width, tex->height)+1;
YughInfo("Has %d mip levels, from wxh %dx%d, pow2 is %ux%u.", mips, tex->width, tex->height,nw,nh); YughInfo("Has %d mip levels, from wxh %dx%d, pow2 is %ux%u.", mips, tex->width, tex->height,nw,nh);
@ -141,7 +142,7 @@ struct texture *texture_from_file(const char *path) {
for (int i = 1; i < mips; i++) { for (int i = 1; i < mips; i++) {
int w, h, mipw, miph; int w, h, mipw, miph;
mip_wh(tex->width, tex->height, &mipw, &miph, i-1); /* mipw miph are previous iteration */ mip_wh(tex->width, tex->height, &mipw, &miph, i-1); // mipw miph are previous iteration
mip_wh(tex->width, tex->height, &w, &h, i); mip_wh(tex->width, tex->height, &w, &h, i);
mipdata[i] = malloc(w * h * 4); mipdata[i] = malloc(w * h * 4);
stbir_resize_uint8_linear(mipdata[i-1], mipw, miph, 0, mipdata[i], w, h, 0, 4); stbir_resize_uint8_linear(mipdata[i-1], mipw, miph, 0, mipdata[i], w, h, 0, 4);
@ -150,18 +151,18 @@ struct texture *texture_from_file(const char *path) {
mipw = w; mipw = w;
miph = h; miph = h;
} }
*/
tex->id = sg_make_image(&(sg_image_desc){ tex->id = sg_make_image(&(sg_image_desc){
.type = SG_IMAGETYPE_2D, .type = SG_IMAGETYPE_2D,
.width = tex->width, .width = tex->width,
.height = tex->height, .height = tex->height,
.usage = SG_USAGE_IMMUTABLE, .usage = SG_USAGE_IMMUTABLE,
.num_mipmaps = mips, //.num_mipmaps = mips,
.data = sg_img_data .data = sg_img_data
}); });
for (int i = 1; i < mips; i++) /*for (int i = 1; i < mips; i++)
free(mipdata[i]); free(mipdata[i]);*/
return tex; return tex;
} }