aseprite importer

This commit is contained in:
John Alanbrook 2023-09-25 13:21:02 +00:00
parent 0256f4cd15
commit d52b15e1be
10 changed files with 92 additions and 1054 deletions

View file

@ -17,6 +17,9 @@ LD = $(CC)
ifeq ($(CC), clang) ifeq ($(CC), clang)
AR = llvm-ar AR = llvm-ar
endif endif
ifeq ($(CC), x86_64-w64-mingw32-gcc)
AR = x86_64-w64-mingw32-ar
endif
ifdef NEDITOR ifdef NEDITOR
CFLAGS += -DNO_EDITOR CFLAGS += -DNO_EDITOR
@ -72,6 +75,7 @@ ifeq ($(OS), Windows_NT)
LDFLAGS += -mwin32 -static LDFLAGS += -mwin32 -static
CFLAGS += -mwin32 CFLAGS += -mwin32
LDLIBS += mingw32 kernel32 d3d11 user32 shell32 dxgi gdi32 ws2_32 ole32 winmm setupapi m LDLIBS += mingw32 kernel32 d3d11 user32 shell32 dxgi gdi32 ws2_32 ole32 winmm setupapi m
# LDLIBS += mingw32 kernel32 gdi32 user32 shell32 ws2_32 ole32 winmm setupapi m
EXT = .exe EXT = .exe
PLAT = w64 PLAT = w64
PKGCMD = cd $(BIN); zip -q -r $(MAKEDIR)/$(DISTDIR)/$(DIST) . -x \*.a ./obj/\* PKGCMD = cd $(BIN); zip -q -r $(MAKEDIR)/$(DISTDIR)/$(DIST) . -x \*.a ./obj/\*
@ -144,7 +148,7 @@ SHADERS := $(patsubst %.sglsl, %.sglsl.h, $(SHADERS))
install: $(BIN)/$(NAME) install: $(BIN)/$(NAME)
cp -f $(BIN)/$(NAME) $(DESTDIR) cp -f $(BIN)/$(NAME) $(DESTDIR)
$(BIN)/$(NAME): $(BIN)/libengine.a $(BIN)/libquickjs.a $(BIN)/libcdb.a $(BIN)/$(NAME): $(BIN)/libengine.a $(BIN)/libquickjs.a
@echo Linking $(NAME) @echo Linking $(NAME)
$(LD) $^ $(LDFLAGS) -L$(BIN) $(LDLIBS) -o $@ $(LD) $^ $(LDFLAGS) -L$(BIN) $(LDLIBS) -o $@
cp $(BIN)/$(NAME) . cp $(BIN)/$(NAME) .
@ -155,13 +159,13 @@ $(DISTDIR)/$(DIST): $(BIN)/$(NAME)
@mkdir -p $(DISTDIR) @mkdir -p $(DISTDIR)
@$(PKGCMD) @$(PKGCMD)
$(BIN)/libengine.a: $(SHADERS) source/engine/core.cdb.h .WAIT $(OBJS) $(BIN)/libengine.a: $(SHADERS) source/engine/core.cdb.h $(OBJS)
@$(AR) rcs $@ $(OBJS) @$(AR) rcs $@ $(OBJS)
$(BIN)/libcdb.a: $(BIN)/libcdb.a:
mkdir -p $(BIN) mkdir -p $(BIN)
rm -f $(CDB)/libcdb.a rm -f $(CDB)/libcdb.a
make -C $(CDB) libcdb.a make -C $(CDB) CC=$(CC) AR=$(AR) libcdb.a
cp $(CDB)/libcdb.a $(BIN) cp $(CDB)/libcdb.a $(BIN)
$(BIN)/libquickjs.a: $(BIN)/libquickjs.a:
@ -211,7 +215,7 @@ jso: tools/jso.c $(BIN)/libquickjs.a
WINCC = x86_64-w64-mingw32-gcc WINCC = x86_64-w64-mingw32-gcc
.PHONY: crosswin .PHONY: crosswin
crosswin: crosswin:
make CC=$(WINCC) OS=Windows_NT gmake CC=$(WINCC) OS=Windows_NT
clean: clean:
@echo Cleaning project @echo Cleaning project

View file

@ -87,6 +87,52 @@ sprite.inputs.kp2 = function() { this.pos = [-0.5,-1]; };
sprite.inputs.kp1 = function() { this.pos = [-1,-1]; }; sprite.inputs.kp1 = function() { this.pos = [-1,-1]; };
Object.seal(sprite); Object.seal(sprite);
var aseframeset2anim = function(frameset, meta)
{
var anim = {};
anim.frames = [];
anim.path = meta.image;
var dim = meta.size;
var ase_make_frame = function(ase_frame,i) {
var f = ase_frame.frame;
var frame = {};
frame.rect = {
s0: f.x/dim.w,
s1: (f.x+f.w)/dim.w,
t0: f.y/dim.h,
t1: (f.y+f.h)/dim.h
};
frame.time = ase_frame.duration / 1000;
anim.frames.push(frame);
};
frameset.forEach(ase_make_frame);
anim.loop = true;
return anim;
}
var ase2anim = function(ase)
{
var json = IO.slurp(ase);
json = JSON.parse(json);
var frames = Array.isArray(json.frames) ? json.frames : Object.values(json.frames);
return aseframeset2anim(json.frames, json.meta);
}
var ase2anims = function(ase)
{
var json = IO.slurp(ase);
json = JSON.parse(json);
var anims = {};
var frames = Array.isArray(json.frames) ? json.frames : Object.values(json.frames);
for (var tag of json.meta.frameTags)
anims[tag.name] = aseframeset2anim(frames.slice(tag.from, tag.to+1), json.meta);
return anims;
}
var gif2anim = function(gif) var gif2anim = function(gif)
{ {
var anim = {}; var anim = {};

View file

@ -3,7 +3,6 @@
#define SOKOL_TRACE_HOOKS #define SOKOL_TRACE_HOOKS
#define SOKOL_IMPL #define SOKOL_IMPL
#include "sokol/sokol_glue.h"
#include "sokol/sokol_audio.h" #include "sokol/sokol_audio.h"
#include "sokol/sokol_time.h" #include "sokol/sokol_time.h"
#include "sokol/sokol_args.h" #include "sokol/sokol_args.h"

View file

@ -93,5 +93,5 @@ void log_cat(FILE *f) {
} }
void sg_logging(const char *tag, uint32_t lvl, uint32_t id, const char *msg, uint32_t line, const char *file, void *data) { void sg_logging(const char *tag, uint32_t lvl, uint32_t id, const char *msg, uint32_t line, const char *file, void *data) {
mYughLog(lvl, 1, line, file, "tag: %d, msg: %s", tag, msg); mYughLog(lvl, 1, line, file, "tag: %s, msg: %s", tag, msg);
} }

View file

@ -1,529 +0,0 @@
#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);
}

View file

@ -1,322 +0,0 @@
/*
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 */

View file

@ -16,7 +16,6 @@
#include "resources.h" #include "resources.h"
#include "yugine.h" #include "yugine.h"
#include "sokol/sokol_app.h" #include "sokol/sokol_app.h"
#include "sokol/sokol_glue.h"
#include "crt.sglsl.h" #include "crt.sglsl.h"
#include "box.sglsl.h" #include "box.sglsl.h"
@ -70,14 +69,16 @@ void gif_rec_start(int w, int h, int cpf, int bitdepth)
.render_target = true, .render_target = true,
.width = gif.w, .width = gif.w,
.height = gif.h, .height = gif.h,
.pixel_format = SG_PIXELFORMAT_RGBA8 // .pixel_format = SG_PIXELFORMAT_RGBA8
.label = "gif rt",
}); });
sg_gif.depth = sg_make_image(&(sg_image_desc){ sg_gif.depth = sg_make_image(&(sg_image_desc){
.render_target = true, .render_target = true,
.width = gif.w, .width = gif.w,
.height = gif.h, .height = gif.h,
.pixel_format = SG_PIXELFORMAT_DEPTH_STENCIL .pixel_format = SG_PIXELFORMAT_DEPTH,
.label = "gif depth",
}); });
sg_gif.pass = sg_make_pass(&(sg_pass_desc){ sg_gif.pass = sg_make_pass(&(sg_pass_desc){
@ -196,6 +197,13 @@ void trace_destroy_shader(sg_shader shd, void *data)
YughWarn("DESTROYED SHADER"); YughWarn("DESTROYED SHADER");
} }
void trace_fail_image(sg_image id, void *data)
{
sg_image_desc desc = sg_query_image_desc(id);
YughWarn("Failed to make image %s", desc.label);
}
static sg_trace_hooks hooks = { static sg_trace_hooks hooks = {
.fail_shader = trace_fail_shader, .fail_shader = trace_fail_shader,
.make_shader = trace_make_shader, .make_shader = trace_make_shader,
@ -207,9 +215,22 @@ void render_init() {
mainwin.height = sapp_height(); mainwin.height = sapp_height();
sg_setup(&(sg_desc){ sg_setup(&(sg_desc){
.context = sapp_sgcontext(), .context.d3d11.device = sapp_d3d11_get_device(),
.context.d3d11.device_context = sapp_d3d11_get_device_context(),
.context.d3d11.render_target_view_cb = sapp_d3d11_get_render_target_view,
.context.d3d11.depth_stencil_view_cb = sapp_d3d11_get_depth_stencil_view,
/* .context.metal.device = sapp_metal_get_device(),
.context.metal.renderpass_descriptor_cb = sapp_metal_get_renderpass_descriptor,
.context.metal.drawable_cb = sapp_metal_get_drawable,
.context.color_format = (sg_pixel_format) sapp_color_format(),
.context.depth_format = (sg_pixel_format) sapp_depth_format(),
.context.sample_count = sapp_sample_count(),
.context.wgpu.device = sapp_wgpu_get_device(),
.context.wgpu.render_view_cb = sapp_wgpu_get_render_view,
.context.wgpu.resolve_view_cb = sapp_wgpu_get_resolve_view,
.context.wgpu.depth_stencil_view_cb = sapp_wgpu_get_depth_stencil_view,
.mtl_force_managed_storage_mode = 1, .mtl_force_managed_storage_mode = 1,
.logger = { */ .logger = {
.func = sg_logging, .func = sg_logging,
.user_data = NULL, .user_data = NULL,
}, },
@ -263,13 +284,15 @@ void render_init() {
.render_target = true, .render_target = true,
.width = mainwin.width, .width = mainwin.width,
.height = mainwin.height, .height = mainwin.height,
.label = "crt rt",
}); });
crt_post.depth_img = sg_make_image(&(sg_image_desc){ crt_post.depth_img = sg_make_image(&(sg_image_desc){
.render_target = true, .render_target = true,
.width = mainwin.width, .width = mainwin.width,
.height = mainwin.height, .height = mainwin.height,
.pixel_format = SG_PIXELFORMAT_DEPTH_STENCIL .pixel_format = SG_PIXELFORMAT_DEPTH,
.label = "crt depth",
}); });
crt_post.pass = sg_make_pass(&(sg_pass_desc){ crt_post.pass = sg_make_pass(&(sg_pass_desc){
@ -370,14 +393,14 @@ void render_winsize()
crt_post.img = sg_make_image(&(sg_image_desc){ crt_post.img = sg_make_image(&(sg_image_desc){
.render_target = true, .render_target = true,
.width = mainwin.width, .width = mainwin.width,
.height = mainwin.height .height = mainwin.height,
}); });
crt_post.depth_img = sg_make_image(&(sg_image_desc){ crt_post.depth_img = sg_make_image(&(sg_image_desc){
.render_target = true, .render_target = true,
.width = mainwin.width, .width = mainwin.width,
.height = mainwin.height, .height = mainwin.height,
.pixel_format = SG_PIXELFORMAT_DEPTH_STENCIL .pixel_format = SG_PIXELFORMAT_DEPTH,
}); });
crt_post.pass = sg_make_pass(&(sg_pass_desc){ crt_post.pass = sg_make_pass(&(sg_pass_desc){

View file

@ -6,7 +6,6 @@
#include <math.h> #include <math.h>
#include <stb_ds.h> #include <stb_ds.h>
#include <stb_image.h> #include <stb_image.h>
#include "gifdec.h"
#include "resources.h" #include "resources.h"
@ -72,22 +71,8 @@ int mip_wh(int w, int h, int *mw, int *mh, int lvl)
int gif_nframes(const char *path) int gif_nframes(const char *path)
{ {
long rawlen; struct Texture *t = texture_pullfromfile(path);
unsigned char *raw = slurp_file(path, &rawlen); return t->frames;
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 */ /* If an empty string or null is put for path, loads default texture */
@ -122,43 +107,9 @@ struct Texture *texture_pullfromfile(const char *path) {
tex->height = qoi.height; tex->height = qoi.height;
n = qoi.channels; n = qoi.channels;
} else if (!strcmp(ext, ".gif")) { } 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 *delays;
int frames; data = stbi_load_gif_from_memory(raw, rawlen, &delays, &tex->width, &tex->height, &tex->frames, &n, 4);
data = stbi_load_gif_from_memory(raw, rawlen, &delays, &tex->width, &tex->height, &frames, &n, 4); tex->height *= tex->frames;
*/
} else { } else {
data = stbi_load_from_memory(raw, rawlen, &tex->width, &tex->height, &n, 4); data = stbi_load_from_memory(raw, rawlen, &tex->width, &tex->height, &n, 4);
} }

View file

@ -58,6 +58,7 @@ struct TextureOptions {
int animation; int animation;
int wrapx; int wrapx;
int wrapy; int wrapy;
}; };
/* Represents an actual texture on the GPU */ /* Represents an actual texture on the GPU */
@ -68,6 +69,7 @@ struct Texture {
unsigned char *data; unsigned char *data;
struct TextureOptions opts; struct TextureOptions opts;
struct TexAnim anim; struct TexAnim anim;
int frames;
}; };
struct Image { struct Image {

View file

@ -1,136 +0,0 @@
#if defined(SOKOL_IMPL) && !defined(SOKOL_GLUE_IMPL)
#define SOKOL_GLUE_IMPL
#endif
#ifndef SOKOL_GLUE_INCLUDED
/*
sokol_glue.h -- glue helper functions for sokol headers
Project URL: https://github.com/floooh/sokol
Do this:
#define SOKOL_IMPL or
#define SOKOL_GLUE_IMPL
before you include this file in *one* C or C++ file to create the
implementation.
...optionally provide the following macros to override defaults:
SOKOL_ASSERT(c) - your own assert macro (default: assert(c))
SOKOL_GLUE_API_DECL - public function declaration prefix (default: extern)
SOKOL_API_DECL - same as SOKOL_GLUE_API_DECL
SOKOL_API_IMPL - public function implementation prefix (default: -)
If sokol_glue.h is compiled as a DLL, define the following before
including the declaration or implementation:
SOKOL_DLL
On Windows, SOKOL_DLL will define SOKOL_GLUE_API_DECL as __declspec(dllexport)
or __declspec(dllimport) as needed.
OVERVIEW
========
The sokol core headers should not depend on each other, but sometimes
it's useful to have a set of helper functions as "glue" between
two or more sokol headers.
This is what sokol_glue.h is for. Simply include the header after other
sokol headers (both for the implementation and declaration), and
depending on what headers have been included before, sokol_glue.h
will make available "glue functions".
PROVIDED FUNCTIONS
==================
- if sokol_app.h and sokol_gfx.h is included:
sg_context_desc sapp_sgcontext(void):
Returns an initialized sg_context_desc function initialized
by calling sokol_app.h functions.
LICENSE
=======
zlib/libpng license
Copyright (c) 2018 Andre Weissflog
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the
use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software in a
product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not
be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#define SOKOL_GLUE_INCLUDED
#if defined(SOKOL_API_DECL) && !defined(SOKOL_GLUE_API_DECL)
#define SOKOL_GLUE_API_DECL SOKOL_API_DECL
#endif
#ifndef SOKOL_GLUE_API_DECL
#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_GLUE_IMPL)
#define SOKOL_GLUE_API_DECL __declspec(dllexport)
#elif defined(_WIN32) && defined(SOKOL_DLL)
#define SOKOL_GLUE_API_DECL __declspec(dllimport)
#else
#define SOKOL_GLUE_API_DECL extern
#endif
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if defined(SOKOL_GFX_INCLUDED) && defined(SOKOL_APP_INCLUDED)
SOKOL_GLUE_API_DECL sg_context_desc sapp_sgcontext(void);
#endif
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* SOKOL_GLUE_INCLUDED */
/*-- IMPLEMENTATION ----------------------------------------------------------*/
#ifdef SOKOL_GLUE_IMPL
#define SOKOL_GLUE_IMPL_INCLUDED (1)
#include <string.h> /* memset */
#ifndef SOKOL_API_IMPL
#define SOKOL_API_IMPL
#endif
#if defined(SOKOL_GFX_INCLUDED) && defined(SOKOL_APP_INCLUDED)
SOKOL_API_IMPL sg_context_desc sapp_sgcontext(void) {
sg_context_desc desc;
memset(&desc, 0, sizeof(desc));
desc.color_format = (sg_pixel_format) sapp_color_format();
desc.depth_format = (sg_pixel_format) sapp_depth_format();
desc.sample_count = sapp_sample_count();
desc.metal.device = sapp_metal_get_device();
desc.metal.renderpass_descriptor_cb = sapp_metal_get_renderpass_descriptor;
desc.metal.drawable_cb = sapp_metal_get_drawable;
desc.d3d11.device = sapp_d3d11_get_device();
desc.d3d11.device_context = sapp_d3d11_get_device_context();
desc.d3d11.render_target_view_cb = sapp_d3d11_get_render_target_view;
desc.d3d11.depth_stencil_view_cb = sapp_d3d11_get_depth_stencil_view;
desc.wgpu.device = sapp_wgpu_get_device();
desc.wgpu.render_view_cb = sapp_wgpu_get_render_view;
desc.wgpu.resolve_view_cb = sapp_wgpu_get_resolve_view;
desc.wgpu.depth_stencil_view_cb = sapp_wgpu_get_depth_stencil_view;
return desc;
}
#endif
#endif /* SOKOL_GLUE_IMPL */