add gif for animations; unique anim types
This commit is contained in:
parent
174a9ed586
commit
0256f4cd15
|
@ -72,11 +72,14 @@ Object.isAccessor = function(obj, prop)
|
|||
|
||||
Object.mergekey = function(o1,o2,k)
|
||||
{
|
||||
if (typeof(o2[k]) === 'object') {
|
||||
if (Object.isAccessor(o2,k))
|
||||
Object.defineProperty(o1, k, Object.getOwnPropertyDescriptor(o2,k));
|
||||
else if (typeof o2[k] === 'object') {
|
||||
if (Array.isArray(o2[k]))
|
||||
o1[k] = o2[k].slice();
|
||||
else
|
||||
else {
|
||||
Object.merge(o1[k], o2[k]);
|
||||
}
|
||||
} else
|
||||
o1[k] = o2[k];
|
||||
}
|
||||
|
|
|
@ -87,18 +87,47 @@ sprite.inputs.kp2 = function() { this.pos = [-0.5,-1]; };
|
|||
sprite.inputs.kp1 = function() { this.pos = [-1,-1]; };
|
||||
Object.seal(sprite);
|
||||
|
||||
var gif2anim = function(gif)
|
||||
{
|
||||
var anim = {};
|
||||
anim.frames = [];
|
||||
anim.path = gif;
|
||||
var frames = cmd(139,gif);
|
||||
Log.warn(`gif has ${frames} frames`);
|
||||
var yslice = 1/frames;
|
||||
for (var f = 0; f < frames; f++) {
|
||||
var frame = {};
|
||||
frame.rect = {
|
||||
s0: 0,
|
||||
s1: 1,
|
||||
t0: yslice*f,
|
||||
t1: yslice*(f+1)
|
||||
};
|
||||
frame.time = 0.05;
|
||||
anim.frames.push(frame);
|
||||
}
|
||||
anim.loop = true;
|
||||
return anim;
|
||||
}
|
||||
|
||||
var strip2anim = function(strip)
|
||||
{
|
||||
var anim = {};
|
||||
anim.frames = [];
|
||||
anim.path = strip;
|
||||
var frames = 8;
|
||||
var xslice = 1/frames;
|
||||
for (var f = 0; f < frames; f++) {
|
||||
var frame = {};
|
||||
frame.rect = {s0:xslice*f, s1: slice*(f+1), t0:0, t1:1};
|
||||
frame.time = 0.05;
|
||||
anim.frames.push(frame);
|
||||
}
|
||||
return anim;
|
||||
}
|
||||
|
||||
/* Container to play sprites and anim2ds */
|
||||
component.char2d = Object.copy(sprite, {
|
||||
frame2rect(frames, frame) {
|
||||
var rect = {s0:0,s1:1,t0:0,t1:1};
|
||||
|
||||
var frameslice = 1/frames;
|
||||
rect.s0 = frameslice*frame;
|
||||
rect.s1 = frameslice*(frame+1);
|
||||
|
||||
return rect;
|
||||
},
|
||||
|
||||
get enabled() { return cmd(114,this.id); },
|
||||
set enabled(x) { cmd(20,this.id,x); },
|
||||
set color(x) { cmd(96,this.id,x); },
|
||||
|
@ -106,74 +135,63 @@ component.char2d = Object.copy(sprite, {
|
|||
set pos(x) { cmd(37,this.id,x); },
|
||||
set layer(x) { cmd(60, this.id, x); },
|
||||
get layer() { return this.gameobject.draw_layer; },
|
||||
|
||||
boundingbox() {
|
||||
var dim = cmd(64,this.path);
|
||||
dim = dim.scale(this.gameobject.scale);
|
||||
dim.x *= 1/6;
|
||||
var realpos = [0,0];
|
||||
// var realpos = this.pos.slice();
|
||||
|
||||
// realpos.x = realpos.x * dim.x + (dim.x/2);
|
||||
// realpos.y = realpos.y * dim.y + (dim.y/2);
|
||||
return cwh2bb(realpos,dim);
|
||||
},
|
||||
boundingbox() {
|
||||
var dim = cmd(64,this.path);
|
||||
dim = dim.scale(this.gameobject.scale);
|
||||
var realpos = [0,0];
|
||||
// var realpos = this.pos.slice();
|
||||
|
||||
sync() {
|
||||
if (this.path)
|
||||
cmd(12,this.id,this.path,this.rect);
|
||||
},
|
||||
// realpos.x = realpos.x * dim.x + (dim.x/2);
|
||||
// realpos.y = realpos.y * dim.y + (dim.y/2);
|
||||
return cwh2bb(realpos,dim);
|
||||
},
|
||||
|
||||
kill() { cmd(9,this.id); },
|
||||
sync() {
|
||||
if (this.path)
|
||||
cmd(12,this.id,this.path,this.rect);
|
||||
},
|
||||
|
||||
kill() { cmd(9,this.id); },
|
||||
ur: {
|
||||
|
||||
},
|
||||
|
||||
|
||||
make(go) {
|
||||
var char = Object.create(this);
|
||||
char.curplaying = char.anims.array()[0];
|
||||
char.obscure('curplaying');
|
||||
char.id = make_sprite(go, char.curplaying.path, this.pos);
|
||||
|
||||
char.obscure('id');
|
||||
Object.assign(char, make_sprite(go));
|
||||
char.frame = 0;
|
||||
char.timer = timer.make(char.advance.bind(char), 1/char.curplaying.fps);
|
||||
char.timer = timer.make(char.advance.bind(char), 1);
|
||||
char.timer.loop = true;
|
||||
char.obscure('timer');
|
||||
// char.obscure('rect');
|
||||
char.rect = {};
|
||||
char.setsprite();
|
||||
return char;
|
||||
},
|
||||
|
||||
frame: 0,
|
||||
|
||||
play(name) {
|
||||
if (!(name in this.anims)) {
|
||||
if (!(name in this)) {
|
||||
Log.info("Can't find an animation named " + name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.curplaying === this.anims[name]) {
|
||||
if (this.curplaying === this[name]) {
|
||||
this.timer.start();
|
||||
return;
|
||||
}
|
||||
|
||||
this.curplaying = this.anims[name];
|
||||
this.timer.time = 1/this.curplaying.fps;
|
||||
this.timer.start();
|
||||
this.curplaying = this[name];
|
||||
this.frame = 0;
|
||||
this.timer.time = this.curplaying.frames[this.frame].time;
|
||||
this.timer.start();
|
||||
this.setsprite();
|
||||
},
|
||||
|
||||
setsprite() {
|
||||
this.path = this.curplaying.path;
|
||||
this.rect = this.frame2rect(this.curplaying.frames, this.frame);
|
||||
cmd(12, this.id, this.path, this.rect);
|
||||
cmd(12, this.id, this.curplaying.path, this.curplaying.frames[this.frame].rect);
|
||||
},
|
||||
|
||||
advance() {
|
||||
this.frame = (this.frame + 1) % this.curplaying.frames;
|
||||
this.frame = (this.frame + 1) % this.curplaying.frames.length;
|
||||
this.setsprite();
|
||||
|
||||
if (this.frame === 0 && !this.curplaying.loop)
|
||||
|
|
|
@ -186,6 +186,7 @@ return;
|
|||
},
|
||||
|
||||
key_move(dir) {
|
||||
if (!editor.grabselect) return;
|
||||
if (Keys.ctrl())
|
||||
this.selectlist.forEach(this.snapper(dir.scale(1.01), editor_config.grid_size));
|
||||
else
|
||||
|
@ -561,7 +562,7 @@ return;
|
|||
GUI.image("icons/icons8-lock-16.png", world2screen(obj.pos));
|
||||
});
|
||||
|
||||
Debug.draw_grid(1, editor_config.grid_size/editor.camera.zoom, Color.Editor.grid.alpha(0.3));
|
||||
Debug.draw_grid(1, editor_config.grid_size, Color.Editor.grid.alpha(0.3));
|
||||
var startgrid = screen2world([-20,Window.height]).map(function(x) { return Math.snap(x, editor_config.grid_size); });
|
||||
var endgrid = screen2world([Window.width, 0]);
|
||||
|
||||
|
@ -1160,6 +1161,14 @@ editor.inputs.mouse.move = function(pos, dpos)
|
|||
});
|
||||
}
|
||||
|
||||
editor.inputs.mouse.scroll = function(scroll)
|
||||
{
|
||||
scroll.y *= -1;
|
||||
editor.grabselect?.forEach(function(x) {
|
||||
x.pos = x.pos.add(scroll.scale(editor.camera.zoom));
|
||||
});
|
||||
}
|
||||
|
||||
editor.inputs.mouse['C-scroll'] = function(scroll)
|
||||
{
|
||||
editor.camera.pos = editor.camera.pos.sub(scroll.scale(editor.camera.zoom * 3).scale([1,-1]));
|
||||
|
|
|
@ -221,7 +221,7 @@ var timer = {
|
|||
return;
|
||||
}
|
||||
|
||||
var t = clone(this);
|
||||
var t = Object.create(this);
|
||||
t.callback = fn;
|
||||
var guardfn = function() {
|
||||
if (typeof t.callback === 'function')
|
||||
|
@ -803,7 +803,6 @@ function save_game_configs() {
|
|||
|
||||
load("scripts/physics.js");
|
||||
|
||||
|
||||
Game.view_camera = function(cam)
|
||||
{
|
||||
Game.camera = cam;
|
||||
|
|
|
@ -564,7 +564,7 @@ void draw_grid(float width, float span, struct rgba color)
|
|||
|
||||
fs_params_t pt;
|
||||
pt.thickness = (float)width;
|
||||
pt.span = span;
|
||||
pt.span = span/cam_zoom();
|
||||
memcpy(&pt.color, col, sizeof(float)*4);
|
||||
sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, SG_RANGE_REF(ubo));
|
||||
sg_apply_uniforms(SG_SHADERSTAGE_FS, 0, SG_RANGE_REF(pt));
|
||||
|
|
|
@ -1103,6 +1103,10 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
|
|||
ret = JS_NewInt64(js, jso_file(str));
|
||||
break;
|
||||
|
||||
case 139:
|
||||
str = JS_ToCString(js,argv[1]);
|
||||
ret = JS_NewInt64(js, gif_nframes(str));
|
||||
break;
|
||||
}
|
||||
|
||||
if (str)
|
||||
|
|
|
@ -78,7 +78,8 @@ void font_init() {
|
|||
.label = "text buffer"
|
||||
});
|
||||
|
||||
font = MakeFont("fonts/LessPerfectDOSVGA.ttf", 16);
|
||||
// font = MakeFont("fonts/LessPerfectDOSVGA.ttf", 16);
|
||||
font = MakeFont("fonts/c64.ttf", 8);
|
||||
bind_text.fs.images[0] = font->texID;
|
||||
bind_text.fs.samplers[0] = sg_make_sampler(&(sg_sampler_desc){});
|
||||
}
|
||||
|
@ -147,8 +148,6 @@ struct sFont *MakeFont(const char *fontfile, int height) {
|
|||
.height = packsize,
|
||||
.pixel_format = SG_PIXELFORMAT_R8,
|
||||
.usage = SG_USAGE_IMMUTABLE,
|
||||
// .min_filter = SG_FILTER_NEAREST,
|
||||
// .mag_filter = SG_FILTER_NEAREST,
|
||||
.data.subimage[0][0] = {
|
||||
.ptr = bitmap,
|
||||
.size = packsize * packsize}});
|
||||
|
@ -166,10 +165,9 @@ struct sFont *MakeFont(const char *fontfile, int height) {
|
|||
r.t1 = (glyph.y1) / (float)packsize;
|
||||
|
||||
stbtt_GetCodepointHMetrics(&fontinfo, c, &newfont->Characters[c].Advance, &newfont->Characters[c].leftbearing);
|
||||
newfont->Characters[c].Advance *= newfont->emscale;
|
||||
newfont->Characters[c].leftbearing *= newfont->emscale;
|
||||
|
||||
// newfont->Characters[c].Advance = glyph.xadvance; /* x distance from this char to the next */
|
||||
newfont->Characters[c].Advance = glyph.xadvance; /* x distance from this char to the next */
|
||||
newfont->Characters[c].Size[0] = glyph.x1 - glyph.x0;
|
||||
newfont->Characters[c].Size[1] = glyph.y1 - glyph.y0;
|
||||
newfont->Characters[c].Bearing[0] = glyph.xoff;
|
||||
|
@ -219,7 +217,7 @@ void sdrawCharacter(struct Character c, HMM_Vec2 cursor, float scale, struct rgb
|
|||
|
||||
float lsize = 1.0 / 1024.0;
|
||||
|
||||
float oline = 0.0;
|
||||
float oline = 1.0;
|
||||
|
||||
vert.pos.x = cursor.X + c.Bearing[0] * scale + oline;
|
||||
vert.pos.y = cursor.Y - c.Bearing[1] * scale - oline;
|
||||
|
@ -312,6 +310,7 @@ struct boundingbox text_bb(const char *text, float scale, float lw, float tracki
|
|||
return cwh2bb((HMM_Vec2){0,0}, (HMM_Vec2){width,height});
|
||||
}
|
||||
|
||||
/* pos given in screen coordinates */
|
||||
int renderText(const char *text, HMM_Vec2 pos, float scale, struct rgba color, float lw, int caret, float tracking) {
|
||||
int len = strlen(text);
|
||||
drawcaret = caret;
|
||||
|
@ -338,8 +337,8 @@ int renderText(const char *text, HMM_Vec2 pos, float scale, struct rgba color, f
|
|||
int wordWidth = 0;
|
||||
|
||||
while (!isspace(*line) && *line != '\0') {
|
||||
wordWidth += font->Characters[*line].Advance * tracking * scale;
|
||||
line++;
|
||||
wordWidth += font->Characters[*line].Advance * tracking * scale;
|
||||
line++;
|
||||
}
|
||||
|
||||
if (lw > 0 && (cursor.X + wordWidth - pos.X) >= lw) {
|
||||
|
|
529
source/engine/gifdec.c
Normal file
529
source/engine/gifdec.c
Normal file
|
@ -0,0 +1,529 @@
|
|||
#include "gifdec.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#define MIN(A, B) ((A) < (B) ? (A) : (B))
|
||||
#define MAX(A, B) ((A) > (B) ? (A) : (B))
|
||||
|
||||
typedef struct Entry {
|
||||
uint16_t length;
|
||||
uint16_t prefix;
|
||||
uint8_t suffix;
|
||||
} Entry;
|
||||
|
||||
typedef struct Table {
|
||||
int bulk;
|
||||
int nentries;
|
||||
Entry *entries;
|
||||
} Table;
|
||||
|
||||
static uint16_t
|
||||
read_num(FILE *f)
|
||||
{
|
||||
uint8_t bytes[2];
|
||||
|
||||
fread(bytes, 2, 1, f);
|
||||
return bytes[0] + (((uint16_t) bytes[1]) << 8);
|
||||
}
|
||||
|
||||
gd_GIF *gd_open_gif_f(FILE *f)
|
||||
{
|
||||
uint8_t sigver[3];
|
||||
uint16_t width, height, depth;
|
||||
uint8_t fdsz, bgidx, aspect;
|
||||
int i;
|
||||
uint8_t *bgcolor;
|
||||
int gct_sz;
|
||||
gd_GIF *gif;
|
||||
|
||||
/* Header */
|
||||
fread(sigver, 1, 3, f);
|
||||
if (memcmp(sigver, "GIF", 3) != 0) {
|
||||
fprintf(stderr, "invalid signature\n");
|
||||
goto fail;
|
||||
}
|
||||
/* Version */
|
||||
fread(sigver, 1,3,f);
|
||||
if (memcmp(sigver, "89a", 3) != 0) {
|
||||
fprintf(stderr, "invalid version\n");
|
||||
goto fail;
|
||||
}
|
||||
/* Width x Height */
|
||||
width = read_num(f);
|
||||
height = read_num(f);
|
||||
/* FDSZ */
|
||||
fread(&fdsz, 1, 1, f);
|
||||
/* Presence of GCT */
|
||||
if (!(fdsz & 0x80)) {
|
||||
fprintf(stderr, "no global color table\n");
|
||||
goto fail;
|
||||
}
|
||||
/* Color Space's Depth */
|
||||
depth = ((fdsz >> 4) & 7) + 1;
|
||||
/* Ignore Sort Flag. */
|
||||
/* GCT Size */
|
||||
gct_sz = 1 << ((fdsz & 0x07) + 1);
|
||||
/* Background Color Index */
|
||||
fread(&bgidx, 1, 1, f);
|
||||
/* Aspect Ratio */
|
||||
fread(&aspect, 1, 1, f);
|
||||
/* Create gd_GIF Structure. */
|
||||
gif = calloc(1, sizeof(*gif));
|
||||
if (!gif) goto fail;
|
||||
gif->fd = f;
|
||||
gif->width = width;
|
||||
gif->height = height;
|
||||
gif->depth = depth;
|
||||
/* Read GCT */
|
||||
gif->gct.size = gct_sz;
|
||||
fread(gif->gct.colors, gif->gct.size, 3, f);
|
||||
gif->palette = &gif->gct;
|
||||
gif->bgindex = bgidx;
|
||||
gif->frame = calloc(4, width * height);
|
||||
if (!gif->frame) {
|
||||
free(gif);
|
||||
goto fail;
|
||||
}
|
||||
gif->canvas = &gif->frame[width * height];
|
||||
if (gif->bgindex)
|
||||
memset(gif->frame, gif->bgindex, gif->width * gif->height);
|
||||
bgcolor = &gif->palette->colors[gif->bgindex*3];
|
||||
if (bgcolor[0] || bgcolor[1] || bgcolor [2])
|
||||
for (i = 0; i < gif->width * gif->height; i++)
|
||||
memcpy(&gif->canvas[i*3], bgcolor, 3);
|
||||
gif->anim_start = ftell(f);
|
||||
goto ok;
|
||||
fail:
|
||||
return NULL;
|
||||
ok:
|
||||
return gif;
|
||||
}
|
||||
|
||||
gd_GIF *
|
||||
gd_open_gif(const char *fname)
|
||||
{
|
||||
FILE *f = fopen(fname, "rb");
|
||||
if (!f) return NULL;
|
||||
return gd_open_gif_f(f);
|
||||
}
|
||||
|
||||
static void
|
||||
discard_sub_blocks(gd_GIF *gif)
|
||||
{
|
||||
uint8_t size;
|
||||
|
||||
do {
|
||||
fread(&size, 1, 1, gif->fd);
|
||||
fseek(gif->fd, size, SEEK_CUR);
|
||||
} while (size);
|
||||
}
|
||||
|
||||
static void
|
||||
read_plain_text_ext(gd_GIF *gif)
|
||||
{
|
||||
if (gif->plain_text) {
|
||||
uint16_t tx, ty, tw, th;
|
||||
uint8_t cw, ch, fg, bg;
|
||||
off_t sub_block;
|
||||
fseek(gif->fd, 1, SEEK_CUR); /* block size = 12 */
|
||||
tx = read_num(gif->fd);
|
||||
ty = read_num(gif->fd);
|
||||
tw = read_num(gif->fd);
|
||||
th = read_num(gif->fd);
|
||||
fread(&cw, 1, 1, gif->fd);
|
||||
fread(&ch, 1, 1, gif->fd);
|
||||
fread(&fg, 1, 1, gif->fd);
|
||||
fread(&bg, 1, 1, gif->fd);
|
||||
sub_block = ftell(gif->fd);
|
||||
gif->plain_text(gif, tx, ty, tw, th, cw, ch, fg, bg);
|
||||
fseek(gif->fd, sub_block, SEEK_SET);
|
||||
} else {
|
||||
/* Discard plain text metadata. */
|
||||
fseek(gif->fd, 13, SEEK_CUR);
|
||||
}
|
||||
/* Discard plain text sub-blocks. */
|
||||
discard_sub_blocks(gif);
|
||||
}
|
||||
|
||||
static void
|
||||
read_graphic_control_ext(gd_GIF *gif)
|
||||
{
|
||||
uint8_t rdit;
|
||||
|
||||
/* Discard block size (always 0x04). */
|
||||
fseek(gif->fd, 1, SEEK_CUR);
|
||||
fread(&rdit, 1, 1, gif->fd);
|
||||
gif->gce.disposal = (rdit >> 2) & 3;
|
||||
gif->gce.input = rdit & 2;
|
||||
gif->gce.transparency = rdit & 1;
|
||||
gif->gce.delay = read_num(gif->fd);
|
||||
fread(&gif->gce.tindex, 1, 1, gif->fd);
|
||||
/* Skip block terminator. */
|
||||
fseek(gif->fd, 1, SEEK_CUR);
|
||||
}
|
||||
|
||||
static void
|
||||
read_comment_ext(gd_GIF *gif)
|
||||
{
|
||||
if (gif->comment) {
|
||||
off_t sub_block = ftell(gif->fd);
|
||||
gif->comment(gif);
|
||||
fseek(gif->fd, sub_block, SEEK_SET);
|
||||
}
|
||||
/* Discard comment sub-blocks. */
|
||||
discard_sub_blocks(gif);
|
||||
}
|
||||
|
||||
static void
|
||||
read_application_ext(gd_GIF *gif)
|
||||
{
|
||||
char app_id[8];
|
||||
char app_auth_code[3];
|
||||
|
||||
/* Discard block size (always 0x0B). */
|
||||
fseek(gif->fd, 1, SEEK_CUR);
|
||||
/* Application Identifier. */
|
||||
fread(app_id, 8, 1, gif->fd);
|
||||
/* Application Authentication Code. */
|
||||
fread(app_auth_code, 3, 1, gif->fd);
|
||||
if (!strncmp(app_id, "NETSCAPE", sizeof(app_id))) {
|
||||
/* Discard block size (0x03) and constant byte (0x01). */
|
||||
fseek(gif->fd, 2, SEEK_CUR);
|
||||
gif->loop_count = read_num(gif->fd);
|
||||
/* Skip block terminator. */
|
||||
fseek(gif->fd, 1, SEEK_CUR);
|
||||
} else if (gif->application) {
|
||||
off_t sub_block = ftell(gif->fd);
|
||||
gif->application(gif, app_id, app_auth_code);
|
||||
fseek(gif->fd, sub_block, SEEK_SET);
|
||||
discard_sub_blocks(gif);
|
||||
} else {
|
||||
discard_sub_blocks(gif);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
read_ext(gd_GIF *gif)
|
||||
{
|
||||
uint8_t label;
|
||||
|
||||
fread(&label, 1, 1, gif->fd);
|
||||
switch (label) {
|
||||
case 0x01:
|
||||
read_plain_text_ext(gif);
|
||||
break;
|
||||
case 0xF9:
|
||||
read_graphic_control_ext(gif);
|
||||
break;
|
||||
case 0xFE:
|
||||
read_comment_ext(gif);
|
||||
break;
|
||||
case 0xFF:
|
||||
read_application_ext(gif);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "unknown extension: %02X\n", label);
|
||||
}
|
||||
}
|
||||
|
||||
static Table *
|
||||
new_table(int key_size)
|
||||
{
|
||||
int key;
|
||||
int init_bulk = MAX(1 << (key_size + 1), 0x100);
|
||||
Table *table = malloc(sizeof(*table) + sizeof(Entry) * init_bulk);
|
||||
if (table) {
|
||||
table->bulk = init_bulk;
|
||||
table->nentries = (1 << key_size) + 2;
|
||||
table->entries = (Entry *) &table[1];
|
||||
for (key = 0; key < (1 << key_size); key++)
|
||||
table->entries[key] = (Entry) {1, 0xFFF, key};
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
/* Add table entry. Return value:
|
||||
* 0 on success
|
||||
* +1 if key size must be incremented after this addition
|
||||
* -1 if could not realloc table */
|
||||
static int
|
||||
add_entry(Table **tablep, uint16_t length, uint16_t prefix, uint8_t suffix)
|
||||
{
|
||||
Table *table = *tablep;
|
||||
if (table->nentries == table->bulk) {
|
||||
table->bulk *= 2;
|
||||
table = realloc(table, sizeof(*table) + sizeof(Entry) * table->bulk);
|
||||
if (!table) return -1;
|
||||
table->entries = (Entry *) &table[1];
|
||||
*tablep = table;
|
||||
}
|
||||
table->entries[table->nentries] = (Entry) {length, prefix, suffix};
|
||||
table->nentries++;
|
||||
if ((table->nentries & (table->nentries - 1)) == 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint16_t
|
||||
get_key(gd_GIF *gif, int key_size, uint8_t *sub_len, uint8_t *shift, uint8_t *byte)
|
||||
{
|
||||
int bits_read;
|
||||
int rpad;
|
||||
int frag_size;
|
||||
uint16_t key;
|
||||
|
||||
key = 0;
|
||||
for (bits_read = 0; bits_read < key_size; bits_read += frag_size) {
|
||||
rpad = (*shift + bits_read) % 8;
|
||||
if (rpad == 0) {
|
||||
/* Update byte. */
|
||||
if (*sub_len == 0) {
|
||||
fread(sub_len, 1, 1, gif->fd); /* Must be nonzero! */
|
||||
if (*sub_len == 0)
|
||||
return 0x1000;
|
||||
}
|
||||
fread(byte, 1, 1, gif->fd);
|
||||
(*sub_len)--;
|
||||
}
|
||||
frag_size = MIN(key_size - bits_read, 8 - rpad);
|
||||
key |= ((uint16_t) ((*byte) >> rpad)) << bits_read;
|
||||
}
|
||||
/* Clear extra bits to the left. */
|
||||
key &= (1 << key_size) - 1;
|
||||
*shift = (*shift + key_size) % 8;
|
||||
return key;
|
||||
}
|
||||
|
||||
/* Compute output index of y-th input line, in frame of height h. */
|
||||
static int
|
||||
interlaced_line_index(int h, int y)
|
||||
{
|
||||
int p; /* number of lines in current pass */
|
||||
|
||||
p = (h - 1) / 8 + 1;
|
||||
if (y < p) /* pass 1 */
|
||||
return y * 8;
|
||||
y -= p;
|
||||
p = (h - 5) / 8 + 1;
|
||||
if (y < p) /* pass 2 */
|
||||
return y * 8 + 4;
|
||||
y -= p;
|
||||
p = (h - 3) / 4 + 1;
|
||||
if (y < p) /* pass 3 */
|
||||
return y * 4 + 2;
|
||||
y -= p;
|
||||
/* pass 4 */
|
||||
return y * 2 + 1;
|
||||
}
|
||||
|
||||
/* Decompress image pixels.
|
||||
* Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table). */
|
||||
static int
|
||||
read_image_data(gd_GIF *gif, int interlace)
|
||||
{
|
||||
uint8_t sub_len, shift, byte;
|
||||
int init_key_size, key_size, table_is_full;
|
||||
int frm_off, frm_size, str_len, i, p, x, y;
|
||||
uint16_t key, clear, stop;
|
||||
int ret;
|
||||
Table *table;
|
||||
Entry entry;
|
||||
off_t start, end;
|
||||
|
||||
fread(&byte, 1, 1, gif->fd);
|
||||
key_size = (int) byte;
|
||||
if (key_size < 2 || key_size > 8)
|
||||
return -1;
|
||||
|
||||
start = ftell(gif->fd);
|
||||
discard_sub_blocks(gif);
|
||||
end = ftell(gif->fd);
|
||||
fseek(gif->fd, start, SEEK_SET);
|
||||
clear = 1 << key_size;
|
||||
stop = clear + 1;
|
||||
table = new_table(key_size);
|
||||
key_size++;
|
||||
init_key_size = key_size;
|
||||
sub_len = shift = 0;
|
||||
key = get_key(gif, key_size, &sub_len, &shift, &byte); /* clear code */
|
||||
frm_off = 0;
|
||||
ret = 0;
|
||||
frm_size = gif->fw*gif->fh;
|
||||
while (frm_off < frm_size) {
|
||||
if (key == clear) {
|
||||
key_size = init_key_size;
|
||||
table->nentries = (1 << (key_size - 1)) + 2;
|
||||
table_is_full = 0;
|
||||
} else if (!table_is_full) {
|
||||
ret = add_entry(&table, str_len + 1, key, entry.suffix);
|
||||
if (ret == -1) {
|
||||
free(table);
|
||||
return -1;
|
||||
}
|
||||
if (table->nentries == 0x1000) {
|
||||
ret = 0;
|
||||
table_is_full = 1;
|
||||
}
|
||||
}
|
||||
key = get_key(gif, key_size, &sub_len, &shift, &byte);
|
||||
if (key == clear) continue;
|
||||
if (key == stop || key == 0x1000) break;
|
||||
if (ret == 1) key_size++;
|
||||
entry = table->entries[key];
|
||||
str_len = entry.length;
|
||||
for (i = 0; i < str_len; i++) {
|
||||
p = frm_off + entry.length - 1;
|
||||
x = p % gif->fw;
|
||||
y = p / gif->fw;
|
||||
if (interlace)
|
||||
y = interlaced_line_index((int) gif->fh, y);
|
||||
gif->frame[(gif->fy + y) * gif->width + gif->fx + x] = entry.suffix;
|
||||
if (entry.prefix == 0xFFF)
|
||||
break;
|
||||
else
|
||||
entry = table->entries[entry.prefix];
|
||||
}
|
||||
frm_off += str_len;
|
||||
if (key < table->nentries - 1 && !table_is_full)
|
||||
table->entries[table->nentries - 1].suffix = entry.suffix;
|
||||
}
|
||||
free(table);
|
||||
if (key == stop)
|
||||
fread(&sub_len, 1, 1, gif->fd); /* Must be zero! */
|
||||
fseek(gif->fd, end, SEEK_SET);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read image.
|
||||
* Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table). */
|
||||
static int
|
||||
read_image(gd_GIF *gif)
|
||||
{
|
||||
uint8_t fisrz;
|
||||
int interlace;
|
||||
|
||||
/* Image Descriptor. */
|
||||
gif->fx = read_num(gif->fd);
|
||||
gif->fy = read_num(gif->fd);
|
||||
|
||||
if (gif->fx >= gif->width || gif->fy >= gif->height)
|
||||
return -1;
|
||||
|
||||
gif->fw = read_num(gif->fd);
|
||||
gif->fh = read_num(gif->fd);
|
||||
|
||||
gif->fw = MIN(gif->fw, gif->width - gif->fx);
|
||||
gif->fh = MIN(gif->fh, gif->height - gif->fy);
|
||||
|
||||
fread(&fisrz, 1, 1, gif->fd);
|
||||
interlace = fisrz & 0x40;
|
||||
/* Ignore Sort Flag. */
|
||||
/* Local Color Table? */
|
||||
if (fisrz & 0x80) {
|
||||
/* Read LCT */
|
||||
gif->lct.size = 1 << ((fisrz & 0x07) + 1);
|
||||
fread(gif->lct.colors, 3, 1, gif->fd);
|
||||
gif->palette = &gif->lct;
|
||||
} else
|
||||
gif->palette = &gif->gct;
|
||||
/* Image Data. */
|
||||
return read_image_data(gif, interlace);
|
||||
}
|
||||
|
||||
static void
|
||||
render_frame_rect(gd_GIF *gif, uint8_t *buffer)
|
||||
{
|
||||
int i, j, k;
|
||||
uint8_t index, *color;
|
||||
i = gif->fy * gif->width + gif->fx;
|
||||
for (j = 0; j < gif->fh; j++) {
|
||||
for (k = 0; k < gif->fw; k++) {
|
||||
index = gif->frame[(gif->fy + j) * gif->width + gif->fx + k];
|
||||
color = &gif->palette->colors[index*3];
|
||||
if (!gif->gce.transparency || index != gif->gce.tindex)
|
||||
memcpy(&buffer[(i+k)*3], color, 3);
|
||||
}
|
||||
i += gif->width;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dispose(gd_GIF *gif)
|
||||
{
|
||||
int i, j, k;
|
||||
uint8_t *bgcolor;
|
||||
switch (gif->gce.disposal) {
|
||||
case 2: /* Restore to background color. */
|
||||
bgcolor = &gif->palette->colors[gif->bgindex*3];
|
||||
i = gif->fy * gif->width + gif->fx;
|
||||
for (j = 0; j < gif->fh; j++) {
|
||||
for (k = 0; k < gif->fw; k++)
|
||||
memcpy(&gif->canvas[(i+k)*3], bgcolor, 3);
|
||||
i += gif->width;
|
||||
}
|
||||
break;
|
||||
case 3: /* Restore to previous, i.e., don't update canvas.*/
|
||||
break;
|
||||
default:
|
||||
/* Add frame non-transparent pixels to canvas. */
|
||||
render_frame_rect(gif, gif->canvas);
|
||||
}
|
||||
}
|
||||
|
||||
/* Return 1 if got a frame; 0 if got GIF trailer; -1 if error. */
|
||||
int
|
||||
gd_get_frame(gd_GIF *gif)
|
||||
{
|
||||
char sep;
|
||||
|
||||
dispose(gif);
|
||||
fread(&sep, 1, 1, gif->fd);
|
||||
while (sep != ',') {
|
||||
if (sep == ';')
|
||||
return 0;
|
||||
if (sep == '!')
|
||||
read_ext(gif);
|
||||
else return -1;
|
||||
fread(&sep, 1, 1, gif->fd);
|
||||
}
|
||||
if (read_image(gif) == -1)
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
gd_render_frame(gd_GIF *gif, uint8_t *buffer)
|
||||
{
|
||||
memcpy(buffer, gif->canvas, gif->width * gif->height * 3);
|
||||
render_frame_rect(gif, buffer);
|
||||
}
|
||||
|
||||
int
|
||||
gd_is_bgcolor(gd_GIF *gif, uint8_t color[3])
|
||||
{
|
||||
return !memcmp(&gif->palette->colors[gif->bgindex*3], color, 3);
|
||||
}
|
||||
|
||||
void
|
||||
gd_rewind(gd_GIF *gif)
|
||||
{
|
||||
fseek(gif->fd, gif->anim_start, SEEK_SET);
|
||||
}
|
||||
|
||||
void
|
||||
gd_close_gif(gd_GIF *gif)
|
||||
{
|
||||
free(gif->frame);
|
||||
free(gif);
|
||||
}
|
322
source/engine/gifdec.h
Normal file
322
source/engine/gifdec.h
Normal file
|
@ -0,0 +1,322 @@
|
|||
/*
|
||||
GIF decoder
|
||||
===========
|
||||
|
||||
This is a small C library that can be used to read GIF files.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* support for all standard GIF features
|
||||
* support for Netscape Application Extension (looping information)
|
||||
* other extensions may be easily supported via user hooks
|
||||
* small and portable: less than 500 lines of C99
|
||||
* public domain
|
||||
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
* no support for GIF files that don't have a global color table
|
||||
* no direct support for the plain text extension (rarely used)
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
0. Essential GIF concepts
|
||||
|
||||
GIF animations are stored in files as a series of palette-based
|
||||
compressed frames.
|
||||
|
||||
In order to display the animation, a program must lay the frames on top
|
||||
of a fixed-size canvas, one after the other. Each frame has a size,
|
||||
position and duration. Each frame can have its own palette or use a
|
||||
global palette defined in the beginning of the file.
|
||||
|
||||
In order to properly use extension hooks, it's necessary to understand
|
||||
how GIF files store variable-sized data. A GIF block of variable size is
|
||||
a sequence of sub-blocks. The first byte in a sub-block indicates the
|
||||
number of data bytes to follow. The end of the block is indicated by an
|
||||
empty sub-block: one byte of value 0x00. For instance, a data block of
|
||||
600 bytes is stored as 4 sub-blocks:
|
||||
|
||||
255, <255 data bytes>, 255, <255 data bytes>, 90, <90 data bytes>, 0
|
||||
|
||||
1. Opening and closing a GIF file
|
||||
|
||||
The function `gd_open_gif()` tries to open a GIF file for reading.
|
||||
|
||||
gd_GIF *gd_open_gif(const char *fname);
|
||||
|
||||
If this function fails, it returns NULL.
|
||||
|
||||
If `gd_open_gif()` succeeds, it returns a GIF handler (`gd_GIF *`). The
|
||||
GIF handler can be passed to the other gifdec functions to decode GIF
|
||||
metadata and frames.
|
||||
|
||||
To close the GIF file and free memory after it has been decoded, the
|
||||
function `gd_close_gif()` must be called.
|
||||
|
||||
void gd_close_gif(gd_GIF *gif);
|
||||
|
||||
2. Reading GIF attributes
|
||||
|
||||
Once a GIF file has been successfully opened, some basic information can
|
||||
be read directly from the GIF handler:
|
||||
|
||||
gd_GIF *gif = gd_open_gif("animation.gif");
|
||||
printf("canvas size: %ux%u\n", gif->width, gif->height);
|
||||
printf("number of colors: %d\n", gif->palette->size);
|
||||
|
||||
3. Reading frames
|
||||
|
||||
The function `gd_get_frame()` decodes one frame from the GIF file.
|
||||
|
||||
int gd_get_frame(gd_GIF *gif);
|
||||
|
||||
This function returns 0 if there are no more frames to read.
|
||||
|
||||
The decoded frame is stored in `gif->frame`, which is a buffer of size
|
||||
`gif->width * gif->height`, in bytes. Each byte value is an index to the
|
||||
palette at `gif->palette`.
|
||||
|
||||
Since GIF files often only store the rectangular region of a frame that
|
||||
changed from the previous frame, this function will only update the
|
||||
bytes in `gif->frame` that are in that region. For GIF files that only
|
||||
use the global palette, the whole state of the canvas is stored in
|
||||
`gif->frame` at all times, in the form of an indexed color image.
|
||||
However, when local palettes are used, it's not enough to keep color
|
||||
indices from previous frames. The color RGB values themselves need to be
|
||||
stored.
|
||||
|
||||
For this reason, in order to get the whole state of the canvas after
|
||||
a new frame has been read, it's necessary to call the function
|
||||
`gd_render_frame()`, which writes all pixels to a given buffer.
|
||||
|
||||
void gd_render_frame(gd_GIF *gif, uint8_t *buffer);
|
||||
|
||||
The buffer size must be at least `gif->width * gif->height * 3`, in
|
||||
bytes. The function `gd_render_frame()` writes the 24-bit RGB values of
|
||||
all canvas pixels in it.
|
||||
|
||||
4. Frame duration
|
||||
|
||||
GIF animations are not required to have a constant frame rate. Each
|
||||
frame can have a different duration, which is stored right before the
|
||||
frame in a Graphic Control Extension (GCE) block. This type of block is
|
||||
read by gifdec into a `gd_GCE` struct that is a member of the GIF
|
||||
handler. Specifically, the unsigned integer `gif->gce.delay` holds the
|
||||
current frame duration, in hundreths of a second. That means that, for
|
||||
instance, if `gif->gce.delay` is `50`, then the current frame must be
|
||||
displayed for half a second.
|
||||
|
||||
5. Looping
|
||||
|
||||
Most GIF animations are supposed to loop automatically, going back to
|
||||
the first frame after the last one is displayed. GIF files may contain
|
||||
looping instruction in the form of a non-negative number. If this number
|
||||
is zero, the animation must loop forever. Otherwise, this number
|
||||
indicates how many times the animation must be played. When `gifdec` is
|
||||
decoding a GIF file, this number is stored in `gif->loop_count`.
|
||||
|
||||
The function `gd_rewind()` must be called to go back to the start of the
|
||||
GIF file without closing and reopening it.
|
||||
|
||||
void gd_rewind(gd_GIF *gif);
|
||||
|
||||
6. Putting it all together
|
||||
|
||||
A simplified skeleton of a GIF viewer may look like this:
|
||||
|
||||
gd_GIF *gif = gd_open_gif("some_animation.gif");
|
||||
char *buffer = malloc(gif->width * gif->height * 3);
|
||||
for (unsigned looped = 1;; looped++) {
|
||||
while (gd_get_frame(gif)) {
|
||||
gd_render_frame(gif, buffer);
|
||||
// insert code to render buffer to screen
|
||||
// and wait for delay time to pass here
|
||||
}
|
||||
if (looped == gif->loop_count)
|
||||
break;
|
||||
gd_rewind(gif);
|
||||
}
|
||||
free(buffer);
|
||||
gd_close_gif(gif);
|
||||
|
||||
7. Transparent Background
|
||||
|
||||
GIFs can mark a certain color in the palette as the "Background Color".
|
||||
Pixels having this color are usually treated as transparent pixels by
|
||||
applications.
|
||||
|
||||
The function `gd_is_bgcolor()` can be used to check whether a pixel in
|
||||
the canvas currently has background color.
|
||||
|
||||
int gd_is_bgcolor(gd_GIF *gif, uint8_t color[3]);
|
||||
|
||||
Here's an example of how to use it:
|
||||
|
||||
gd_render_frame(gif, buffer);
|
||||
color = buffer;
|
||||
for (y = 0; y < gif->height; y++) {
|
||||
for (x = 0; x < gif->width; x++) {
|
||||
if (gd_is_bgcolor(gif, color))
|
||||
transparent_pixel(x, y);
|
||||
else
|
||||
opaque_pixel(x, y, color);
|
||||
color += 3;
|
||||
}
|
||||
}
|
||||
|
||||
8. Reading streamed metadata with extension hooks
|
||||
|
||||
Some metadata blocks may occur any number of times in GIF files in
|
||||
between frames. By default, gifdec ignore these blocks. However, it's
|
||||
possible to setup callback functions to handle each type of extension
|
||||
block, by changing some GIF handler members.
|
||||
|
||||
Whenever a Comment Extension block is found, `gif->comment()` is called.
|
||||
|
||||
void (*comment)(struct gd_GIF *gif);
|
||||
|
||||
As defined in the GIF specification, "[t]he Comment Extension contains
|
||||
textual information which is not part of the actual graphics in the GIF
|
||||
Data Stream." Encoders are recommended to only include "text using the
|
||||
7-bit ASCII character set" in GIF comments.
|
||||
|
||||
The actual comment is stored as a variable-sized block and must be read
|
||||
from the file (using the file descriptor `gif->fd`) by the callback
|
||||
function. Here's an example, printing the comment to stdout:
|
||||
|
||||
void
|
||||
comment(gd_GIF *gif)
|
||||
{
|
||||
uint8_t sub_len, byte, i;
|
||||
do {
|
||||
read(gif->fd, &sub_len, 1);
|
||||
for (i = 0; i < sub_len; i++) {
|
||||
read(gif->fd, &byte, 1);
|
||||
printf("%c", byte);
|
||||
}
|
||||
} while (sub_len);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// Somewhere on the main path of execution.
|
||||
gif->comment = comment;
|
||||
|
||||
|
||||
Whenever a Plain Text Extension block is found, `gif->plain_text()` is
|
||||
called.
|
||||
|
||||
void (*plain_text)(
|
||||
struct gd_GIF *gif, uint16_t tx, uint16_t ty,
|
||||
uint16_t tw, uint16_t th, uint8_t cw, uint8_t ch,
|
||||
uint8_t fg, uint8_t bg
|
||||
);
|
||||
|
||||
According to the GIF specification, "[t]he Plain Text Extension contains
|
||||
textual data and the parameters necessary to render that data as a
|
||||
graphic [...]". This is a rarely used extension that requires the
|
||||
decoder to actually render text on the canvas. In order to support this,
|
||||
one must read the relevant specification and implement a suitable
|
||||
callback function to setup as `gif->plain_text`.
|
||||
|
||||
The actual plain text is stored as a variable-sized block and must be
|
||||
read from the file by the callback function.
|
||||
|
||||
|
||||
Whenever an unknown Application Extension block is found,
|
||||
`gif->application()` is called.
|
||||
|
||||
void (*application)(struct gd_GIF *gif, char id[8], char auth[3]);
|
||||
|
||||
Application Extensions are used to extend GIF with extraofficial
|
||||
features. Currently, gifdec only supports the so-called "Netscape
|
||||
Application Extension", which is commonly used to specify looping
|
||||
behavior. Other Application Extensions may be supported via this hook.
|
||||
|
||||
The application data is stored as a variable-sized block and must be
|
||||
read from the file by the callback function.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
The file "example.c" is a demo GIF player based on gifdec and SDL2. It
|
||||
can be tested like this:
|
||||
|
||||
$ cc `pkg-config --cflags --libs sdl2` -o gifplay gifdec.c example.c
|
||||
$ ./gifplay animation.gif
|
||||
|
||||
That should display the animation. Press SPACE to pause and Q to quit.
|
||||
|
||||
Copying
|
||||
-------
|
||||
|
||||
All of the source code and documentation for gifdec is released into the
|
||||
public domain and provided without warranty of any kind.
|
||||
*/
|
||||
|
||||
#ifndef GIFDEC_H
|
||||
#define GIFDEC_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct gd_Palette {
|
||||
int size;
|
||||
uint8_t colors[0x100 * 3];
|
||||
} gd_Palette;
|
||||
|
||||
typedef struct gd_GCE {
|
||||
uint16_t delay;
|
||||
uint8_t tindex;
|
||||
uint8_t disposal;
|
||||
int input;
|
||||
int transparency;
|
||||
} gd_GCE;
|
||||
|
||||
typedef struct gd_GIF {
|
||||
FILE *fd;
|
||||
off_t anim_start;
|
||||
uint16_t width, height;
|
||||
uint16_t depth;
|
||||
uint16_t loop_count;
|
||||
gd_GCE gce;
|
||||
gd_Palette *palette;
|
||||
gd_Palette lct, gct;
|
||||
void (*plain_text)(
|
||||
struct gd_GIF *gif, uint16_t tx, uint16_t ty,
|
||||
uint16_t tw, uint16_t th, uint8_t cw, uint8_t ch,
|
||||
uint8_t fg, uint8_t bg
|
||||
);
|
||||
void (*comment)(struct gd_GIF *gif);
|
||||
void (*application)(struct gd_GIF *gif, char id[8], char auth[3]);
|
||||
uint16_t fx, fy, fw, fh;
|
||||
uint8_t bgindex;
|
||||
uint8_t *canvas, *frame;
|
||||
} gd_GIF;
|
||||
|
||||
gd_GIF *gd_open_gif(const char *fname);
|
||||
gd_GIF *gd_open_gif_f(FILE *f);
|
||||
int gd_get_frame(gd_GIF *gif);
|
||||
void gd_render_frame(gd_GIF *gif, uint8_t *buffer);
|
||||
int gd_is_bgcolor(gd_GIF *gif, uint8_t color[3]);
|
||||
void gd_rewind(gd_GIF *gif);
|
||||
void gd_close_gif(gd_GIF *gif);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* GIFDEC_H */
|
|
@ -6,6 +6,7 @@
|
|||
#include <math.h>
|
||||
#include <stb_ds.h>
|
||||
#include <stb_image.h>
|
||||
#include "gifdec.h"
|
||||
|
||||
#include "resources.h"
|
||||
|
||||
|
@ -69,6 +70,26 @@ int mip_wh(int w, int h, int *mw, int *mh, int lvl)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int gif_nframes(const char *path)
|
||||
{
|
||||
long rawlen;
|
||||
unsigned char *raw = slurp_file(path, &rawlen);
|
||||
char *ext = strrchr(path,'.');
|
||||
int frames = 0; /* default here is also the error code */
|
||||
|
||||
FILE *f = fmemopen(raw, rawlen, "r");
|
||||
gd_GIF *gif = gd_open_gif_f(f);
|
||||
|
||||
if (!gif) goto fin;
|
||||
|
||||
while (gd_get_frame(gif)) frames++;
|
||||
|
||||
fin:
|
||||
gd_close_gif(gif);
|
||||
free(raw);
|
||||
return frames;
|
||||
}
|
||||
|
||||
/* If an empty string or null is put for path, loads default texture */
|
||||
struct Texture *texture_pullfromfile(const char *path) {
|
||||
if (!path) return texture_notex();
|
||||
|
@ -93,13 +114,51 @@ struct Texture *texture_pullfromfile(const char *path) {
|
|||
unsigned char *data;
|
||||
|
||||
char *ext = strrchr(path, '.');
|
||||
|
||||
if (ext && !strcmp(ext, ".qoi")) {
|
||||
|
||||
if (!strcmp(ext, ".qoi")) {
|
||||
qoi_desc qoi;
|
||||
data = qoi_decode(raw, rawlen, &qoi, 4);
|
||||
tex->width = qoi.width;
|
||||
tex->height = qoi.height;
|
||||
n = qoi.channels;
|
||||
} else if (!strcmp(ext, ".gif")) {
|
||||
FILE *f = fmemopen(raw, rawlen, "r");
|
||||
gd_GIF *gif = gd_open_gif_f(f);
|
||||
int frames = 0;
|
||||
while (gd_get_frame(gif))
|
||||
frames++;
|
||||
|
||||
gd_rewind(gif);
|
||||
|
||||
int frame = 0;
|
||||
uint8_t gifbuf[gif->width*gif->height*3];
|
||||
data = malloc(gif->width*gif->height*4*frames);
|
||||
|
||||
while (gd_get_frame(gif)) {
|
||||
gd_render_frame(gif, gifbuf);
|
||||
uint8_t black[4] = {0,0,0,0};
|
||||
for (int p = 0; p < gif->height*gif->width; p++) {
|
||||
int offset = (p*4)+(frame*gif->width*gif->height*4);
|
||||
if (gd_is_bgcolor(gif, gifbuf+(p*3)))
|
||||
memcpy(data+offset, black, 4*sizeof(uint8_t));
|
||||
else {
|
||||
memcpy(data+offset, gifbuf+(p*3), 3*sizeof(uint8_t));
|
||||
data[offset+3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
frame++;
|
||||
}
|
||||
tex->width = gif->width;
|
||||
tex->height = gif->height*frames;
|
||||
gd_close_gif(gif);
|
||||
fclose(f);
|
||||
|
||||
/*
|
||||
int *delays;
|
||||
int frames;
|
||||
data = stbi_load_gif_from_memory(raw, rawlen, &delays, &tex->width, &tex->height, &frames, &n, 4);
|
||||
*/
|
||||
} else {
|
||||
data = stbi_load_from_memory(raw, rawlen, &tex->width, &tex->height, &n, 4);
|
||||
}
|
||||
|
|
|
@ -99,6 +99,8 @@ void anim_bkwd(struct anim2d *anim);
|
|||
void anim_incr(struct anim2d *anim);
|
||||
void anim_decr(struct anim2d *anim);
|
||||
|
||||
int gif_nframes(const char *path);
|
||||
|
||||
struct glrect tex_get_rect(struct Texture *tex);
|
||||
cpVect tex_get_dimensions(struct Texture *tex);
|
||||
struct glrect anim_get_rect(struct anim2d *anim);
|
||||
|
|
1148
source/engine/thirdparty/sokol/sokol_color.h
vendored
1148
source/engine/thirdparty/sokol/sokol_color.h
vendored
File diff suppressed because it is too large
Load diff
4619
source/engine/thirdparty/sokol/sokol_debugtext.h
vendored
4619
source/engine/thirdparty/sokol/sokol_debugtext.h
vendored
File diff suppressed because it is too large
Load diff
1854
source/engine/thirdparty/sokol/sokol_fontstash.h
vendored
1854
source/engine/thirdparty/sokol/sokol_fontstash.h
vendored
File diff suppressed because it is too large
Load diff
4320
source/engine/thirdparty/sokol/sokol_gl.h
vendored
4320
source/engine/thirdparty/sokol/sokol_gl.h
vendored
File diff suppressed because it is too large
Load diff
3137
source/engine/thirdparty/sokol/sokol_imgui.h
vendored
3137
source/engine/thirdparty/sokol/sokol_imgui.h
vendored
File diff suppressed because it is too large
Load diff
5804
source/engine/thirdparty/sokol/sokol_spine.h
vendored
5804
source/engine/thirdparty/sokol/sokol_spine.h
vendored
File diff suppressed because it is too large
Load diff
|
@ -1,47 +0,0 @@
|
|||
#version 330 core
|
||||
in vec2 TexCoords;
|
||||
in vec4 fColor;
|
||||
in vec2 fst;
|
||||
|
||||
out vec4 color;
|
||||
|
||||
uniform sampler2D text;
|
||||
|
||||
float osize = 1.0;
|
||||
|
||||
void main()
|
||||
{
|
||||
float lettera = texture(text,TexCoords).r;
|
||||
|
||||
if (lettera <= 0.1f)
|
||||
{
|
||||
vec2 uvpos = TexCoords - fst;
|
||||
for (int x = 0; x < 3; x++) {
|
||||
for (int y = 0; y < 3; y++) {
|
||||
float pa = texture(text, uvpos + (fst*vec2(x,y))).r;
|
||||
if (pa > 0.1) {
|
||||
color = vec4(0.0,0.0,0.0, fColor.a);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
discard;
|
||||
}
|
||||
|
||||
// vec2 lsize = fst / textureSize(text,0).xy;
|
||||
/* vec2 uvpos = TexCoords - fst;
|
||||
for (int x = 0; x < 3; x++) {
|
||||
for (int y = 0; 0 < 3; y++) {
|
||||
float pa = texture(text, uvpos + (fst * vec2(x,y))).r;
|
||||
|
||||
if (pa <= 0.1) {
|
||||
color = vec4(0.0,0.0,0.0,fColor.a);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
color = vec4(fColor.xyz, fColor.a);
|
||||
}
|
Loading…
Reference in a new issue