From 0bc96b60f8195e56bebd638d062d08a12f44d374 Mon Sep 17 00:00:00 2001 From: "ndsl7109256@gmail.com" Date: Wed, 28 Aug 2024 17:57:05 +0800 Subject: [PATCH] Support gif animation To enable support for animations for GIF. I add new animation structure twin_animation_t to manage the frames and the timing infomation and new API functions using gifdec[1] to load, manage, and display animations. Besides, I add a new animation app to demonstrate GIF loading and display. [1] https://github.com/lecram/gifdec Close #37 --- Makefile | 5 +- apps/animation.c | 97 ++++++++ apps/apps_animation.h | 17 ++ apps/main.c | 4 + assets/nyancat.gif | Bin 0 -> 10623 bytes configs/Kconfig | 4 + configs/defconfig | 1 + include/twin.h | 27 +++ src/gif.c | 111 +++++++++ src/gifdec.c | 523 ++++++++++++++++++++++++++++++++++++++++++ src/gifdec.h | 60 +++++ 11 files changed, 848 insertions(+), 1 deletion(-) create mode 100644 apps/animation.c create mode 100644 apps/apps_animation.h create mode 100644 assets/nyancat.gif create mode 100644 src/gif.c create mode 100644 src/gifdec.c create mode 100644 src/gifdec.h diff --git a/Makefile b/Makefile index b014a51..db44128 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,9 @@ libtwin.a_files-y = \ src/pixmap.c \ src/timeout.c \ src/image.c \ - src/api.c + src/api.c \ + src/gifdec.c\ + src/gif.c libtwin.a_includes-y := \ include \ @@ -83,6 +85,7 @@ libapps.a_files-$(CONFIG_DEMO_CLOCK) += apps/clock.c libapps.a_files-$(CONFIG_DEMO_CALCULATOR) += apps/calc.c libapps.a_files-$(CONFIG_DEMO_LINE) += apps/line.c libapps.a_files-$(CONFIG_DEMO_SPLINE) += apps/spline.c +libapps.a_files-$(CONFIG_DEMO_ANIMATION) += apps/animation.c libapps.a_includes-y := include diff --git a/apps/animation.c b/apps/animation.c new file mode 100644 index 0000000..055b722 --- /dev/null +++ b/apps/animation.c @@ -0,0 +1,97 @@ +/* + * Twin - A Tiny Window System + * Copyright (c) 2004 Keith Packard + * All rights reserved. + */ + +#include +#include +#include +#include + +#include "twin_private.h" + +#include "apps_animation.h" + +#define _apps_animation_pixmap(animation) ((animation)->widget.window->pixmap) + + +typedef struct { + twin_widget_t widget; + twin_animation_t *gif; + twin_timeout_t *timeout; +} apps_animation_t; + +static void _apps_animation_paint(apps_animation_t *animation) +{ + twin_pixmap_t *current_frame = + twin_animation_get_current_frame(animation->gif); + + twin_operand_t srcop = { + .source_kind = TWIN_PIXMAP, + .u.pixmap = current_frame, + }; + twin_composite(_apps_animation_pixmap(animation), 0, 0, &srcop, 0, 0, NULL, + 0, 0, TWIN_SOURCE, current_frame->width, + current_frame->height); + + twin_animation_advance_frame(animation->gif); +} + +static twin_time_t _apps_animation_timeout(twin_time_t maybe_unused now, + void *closure) +{ + apps_animation_t *animation = closure; + _twin_widget_queue_paint(&animation->widget); + twin_time_t delay = + animation->gif->frame_delays[animation->gif->current_frame]; + return delay; +} + +static twin_dispatch_result_t _apps_animation_dispatch(twin_widget_t *widget, + twin_event_t *event) +{ + apps_animation_t *animation = (apps_animation_t *) widget; + if (_twin_widget_dispatch(widget, event) == TwinDispatchDone) + return TwinDispatchDone; + switch (event->kind) { + case TwinEventPaint: + _apps_animation_paint(animation); + break; + default: + break; + } + return TwinDispatchContinue; +} + +static void _apps_animation_init(apps_animation_t *animation, + twin_box_t *parent, + twin_dispatch_proc_t dispatch) +{ + static const twin_widget_layout_t preferred = {0, 0, 1, 1}; + _twin_widget_init(&animation->widget, parent, 0, preferred, dispatch); + twin_time_t delay = + animation->gif->frame_delays[animation->gif->current_frame]; + animation->timeout = + twin_set_timeout(_apps_animation_timeout, delay, animation); +} + +static apps_animation_t *apps_animation_create(twin_box_t *parent, + twin_animation_t *gif) +{ + apps_animation_t *animation = malloc(sizeof(apps_animation_t)); + animation->gif = gif; + _apps_animation_init(animation, parent, _apps_animation_dispatch); + return animation; +} + +void apps_animation_start(twin_screen_t *screen, const char *path, int x, int y) +{ + twin_animation_t *gif = twin_animation_from_file(path); + twin_toplevel_t *toplevel = + twin_toplevel_create(screen, TWIN_ARGB32, TwinWindowApplication, x, y, + gif->width, gif->height, path); + apps_animation_t *animation = apps_animation_create(&toplevel->box, gif); + (void) animation; + twin_toplevel_show(toplevel); +} diff --git a/apps/apps_animation.h b/apps/apps_animation.h new file mode 100644 index 0000000..f7110ff --- /dev/null +++ b/apps/apps_animation.h @@ -0,0 +1,17 @@ +/* + * Twin - A Tiny Window System + * Copyright (c) 2004 Keith Packard + * All rights reserved. + */ + +#ifndef _APPS_ANIMATION_H_ +#define _APPS_ANIMATION_H_ + +#include + +void apps_animation_start(twin_screen_t *screen, + const char *name, + int x, + int y); + +#endif /* _APPS_ANIMATION_H_ */ diff --git a/apps/main.c b/apps/main.c index 9c6a411..41ed8ef 100644 --- a/apps/main.c +++ b/apps/main.c @@ -13,6 +13,7 @@ #include #include +#include "apps_animation.h" #include "apps_calc.h" #include "apps_clock.h" #include "apps_hello.h" @@ -122,6 +123,9 @@ int main(void) #if defined(CONFIG_DEMO_SPLINE) apps_spline_start(tx->screen, "Spline", 20, 20, 400, 400); #endif +#if defined(CONFIG_DEMO_ANIMATION) + apps_animation_start(tx->screen, ASSET_PATH "nyancat.gif", 20, 20); +#endif twin_dispatch(); diff --git a/assets/nyancat.gif b/assets/nyancat.gif new file mode 100644 index 0000000000000000000000000000000000000000..b6ec3845f0d7e2209f4f4b8f8a834b7af6fbe1d7 GIT binary patch literal 10623 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 literal 0 HcmV?d00001 diff --git a/configs/Kconfig b/configs/Kconfig index 3567484..b682e77 100644 --- a/configs/Kconfig +++ b/configs/Kconfig @@ -72,4 +72,8 @@ config DEMO_SPLINE default y depends on DEMO_APPLICATIONS +config DEMO_ANIMATION + bool "Build animation demo" + default y + depends on DEMO_APPLICATIONS endmenu diff --git a/configs/defconfig b/configs/defconfig index 2bd1efc..3e3e5ed 100644 --- a/configs/defconfig +++ b/configs/defconfig @@ -8,3 +8,4 @@ CONFIG_DEMO_CLOCK=y CONFIG_DEMO_CALCULATOR=y CONFIG_DEMO_LINE=y CONFIG_DEMO_SPLINE=y +CONFIG_DEMO_ANIMATION=y diff --git a/include/twin.h b/include/twin.h index a04b095..54ca992 100644 --- a/include/twin.h +++ b/include/twin.h @@ -179,6 +179,21 @@ typedef struct _twin_pixmap { twin_window_t *window; } twin_pixmap_t; +typedef struct _twin_animation { + /* Array of pixmaps representing each frame of the animation */ + twin_pixmap_t **frames; + /* Number of frames in the animation */ + twin_count_t n_frames; + /* Delay between frames in milliseconds */ + twin_count_t *frame_delays; + /* Whether the animation should loop */ + bool loop; + /* Current frame index */ + twin_count_t current_frame; + twin_coord_t width; /* pixels */ + twin_coord_t height; /* pixels */ +} twin_animation_t; + /* * twin_put_begin_t: called before data are drawn to the screen * twin_put_span_t: called for each scanline drawn @@ -692,6 +707,18 @@ void twin_icon_draw(twin_pixmap_t *pixmap, twin_pixmap_t *twin_pixmap_from_file(const char *path, twin_format_t fmt); +/* + * gif.c + */ + +twin_animation_t *twin_animation_from_file(const char *path); + +twin_pixmap_t *twin_animation_get_current_frame(twin_animation_t *animation); + +void twin_animation_advance_frame(twin_animation_t *animation); + +void twin_animation_destroy(twin_animation_t *animation); + /* * label.c */ diff --git a/src/gif.c b/src/gif.c new file mode 100644 index 0000000..c6d8f55 --- /dev/null +++ b/src/gif.c @@ -0,0 +1,111 @@ +#include +#include +#include +#include "gifdec.h" +#include "twin.h" + +twin_animation_t *twin_animation_from_file(const char *path) +{ + twin_animation_t *animation = malloc(sizeof(twin_animation_t)); + if (!animation) + return NULL; + + gd_GIF *gif = gd_open_gif(path); + if (!gif) { + free(animation); + return NULL; + } + + animation->n_frames = 0; + animation->frames = NULL; + animation->frame_delays = NULL; + animation->loop = gif->loop_count == 0; + animation->current_frame = 0; + animation->width = gif->width; + animation->height = gif->height; + + int frame_count = 0; + while (gd_get_frame(gif)) { + frame_count++; + } + + animation->n_frames = frame_count; + animation->frames = malloc(sizeof(twin_pixmap_t *) * frame_count); + animation->frame_delays = malloc(sizeof(twin_count_t) * frame_count); + + gd_rewind(gif); + uint8_t *color, *frame; + frame = malloc(gif->width * gif->height * 3); + if (!frame) { + free(animation); + gd_close_gif(gif); + return NULL; + } + for (twin_count_t i = 0; i < frame_count; i++) { + animation->frames[i] = + twin_pixmap_create(TWIN_ARGB32, gif->width, gif->height); + animation->frames[i]->format = TWIN_ARGB32; + + gd_render_frame(gif, frame); + color = frame; + twin_pointer_t p = twin_pixmap_pointer(animation->frames[i], 0, 0); + twin_coord_t row = 0, col = 0; + for (int j = 0; j < gif->width * gif->height; j++) { + uint8_t r = *(color++); + uint8_t g = *(color++); + uint8_t b = *(color++); + if (!gd_is_bgcolor(gif, color)) + *(p.argb32++) = 0xFF000000U | (r << 16) | (g << 8) | b; + /* Construct background */ + else if (((row >> 3) + (col >> 3)) & 1) + *(p.argb32++) = 0xFFAFAFAFU; + else + *(p.argb32++) = 0xFF7F7F7FU; + col++; + if (col == gif->width) { + row++; + col = 0; + } + } + + animation->frame_delays[i] = + gif->gce.delay * 10; // GIF delay in units of 1/100 second + gd_get_frame(gif); + } + gd_close_gif(gif); + return animation; +} + +twin_pixmap_t *twin_animation_get_current_frame(twin_animation_t *animation) +{ + if (!animation || animation->current_frame >= animation->n_frames) { + return NULL; + } + return animation->frames[animation->current_frame]; +} + +void twin_animation_advance_frame(twin_animation_t *animation) +{ + if (!animation) + return; + + animation->current_frame++; + if (animation->current_frame >= animation->n_frames) { + if (animation->loop) { + animation->current_frame = 0; + } + } +} + +void twin_animation_destroy(twin_animation_t *animation) +{ + if (!animation) + return; + + for (twin_count_t i = 0; i < animation->n_frames; i++) { + twin_pixmap_destroy(animation->frames[i]); + } + free(animation->frames); + free(animation->frame_delays); + free(animation); +} diff --git a/src/gifdec.c b/src/gifdec.c new file mode 100644 index 0000000..e0b3167 --- /dev/null +++ b/src/gifdec.c @@ -0,0 +1,523 @@ +#include "gifdec.h" + +#include +#include +#include + +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#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(int fd) +{ + uint8_t bytes[2]; + + read(fd, bytes, 2); + return bytes[0] + (((uint16_t) bytes[1]) << 8); +} + +gd_GIF *gd_open_gif(const char *fname) +{ + int fd; + 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; + + fd = open(fname, O_RDONLY); + if (fd == -1) + return NULL; +#ifdef _WIN32 + setmode(fd, O_BINARY); +#endif + /* Header */ + read(fd, sigver, 3); + if (memcmp(sigver, "GIF", 3) != 0) { + fprintf(stderr, "invalid signature\n"); + goto fail; + } + /* Version */ + read(fd, sigver, 3); + if (memcmp(sigver, "89a", 3) != 0) { + fprintf(stderr, "invalid version\n"); + goto fail; + } + /* Width x Height */ + width = read_num(fd); + height = read_num(fd); + /* FDSZ */ + read(fd, &fdsz, 1); + /* 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 */ + read(fd, &bgidx, 1); + /* Aspect Ratio */ + read(fd, &aspect, 1); + /* Create gd_GIF Structure. */ + gif = calloc(1, sizeof(*gif)); + if (!gif) + goto fail; + gif->fd = fd; + gif->width = width; + gif->height = height; + gif->depth = depth; + /* Read GCT */ + gif->gct.size = gct_sz; + read(fd, gif->gct.colors, 3 * gif->gct.size); + 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 = lseek(fd, 0, SEEK_CUR); + goto ok; +fail: + close(fd); + return 0; +ok: + return gif; +} + +static void discard_sub_blocks(gd_GIF *gif) +{ + uint8_t size; + + do { + read(gif->fd, &size, 1); + lseek(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; + lseek(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); + read(gif->fd, &cw, 1); + read(gif->fd, &ch, 1); + read(gif->fd, &fg, 1); + read(gif->fd, &bg, 1); + sub_block = lseek(gif->fd, 0, SEEK_CUR); + gif->plain_text(gif, tx, ty, tw, th, cw, ch, fg, bg); + lseek(gif->fd, sub_block, SEEK_SET); + } else { + /* Discard plain text metadata. */ + lseek(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). */ + lseek(gif->fd, 1, SEEK_CUR); + read(gif->fd, &rdit, 1); + gif->gce.disposal = (rdit >> 2) & 3; + gif->gce.input = rdit & 2; + gif->gce.transparency = rdit & 1; + gif->gce.delay = read_num(gif->fd); + read(gif->fd, &gif->gce.tindex, 1); + /* Skip block terminator. */ + lseek(gif->fd, 1, SEEK_CUR); +} + +static void read_comment_ext(gd_GIF *gif) +{ + if (gif->comment) { + off_t sub_block = lseek(gif->fd, 0, SEEK_CUR); + gif->comment(gif); + lseek(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). */ + lseek(gif->fd, 1, SEEK_CUR); + /* Application Identifier. */ + read(gif->fd, app_id, 8); + /* Application Authentication Code. */ + read(gif->fd, app_auth_code, 3); + if (!strncmp(app_id, "NETSCAPE", sizeof(app_id))) { + /* Discard block size (0x03) and constant byte (0x01). */ + lseek(gif->fd, 2, SEEK_CUR); + gif->loop_count = read_num(gif->fd); + /* Skip block terminator. */ + lseek(gif->fd, 1, SEEK_CUR); + } else if (gif->application) { + off_t sub_block = lseek(gif->fd, 0, SEEK_CUR); + gif->application(gif, app_id, app_auth_code); + lseek(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; + + read(gif->fd, &label, 1); + 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) { + read(gif->fd, sub_len, 1); /* Must be nonzero! */ + if (*sub_len == 0) + return 0x1000; + } + read(gif->fd, byte, 1); + (*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; + + read(gif->fd, &byte, 1); + key_size = (int) byte; + if (key_size < 2 || key_size > 8) + return -1; + + start = lseek(gif->fd, 0, SEEK_CUR); + discard_sub_blocks(gif); + end = lseek(gif->fd, 0, SEEK_CUR); + lseek(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) + read(gif->fd, &sub_len, 1); /* Must be zero! */ + lseek(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); + + read(gif->fd, &fisrz, 1); + interlace = fisrz & 0x40; + /* Ignore Sort Flag. */ + /* Local Color Table? */ + if (fisrz & 0x80) { + /* Read LCT */ + gif->lct.size = 1 << ((fisrz & 0x07) + 1); + read(gif->fd, gif->lct.colors, 3 * gif->lct.size); + 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); + read(gif->fd, &sep, 1); + while (sep != ',') { + if (sep == ';') + return 0; + if (sep == '!') + read_ext(gif); + else + return -1; + read(gif->fd, &sep, 1); + } + 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) +{ + lseek(gif->fd, gif->anim_start, SEEK_SET); +} + +void gd_close_gif(gd_GIF *gif) +{ + close(gif->fd); + free(gif->frame); + free(gif); +} diff --git a/src/gifdec.h b/src/gifdec.h new file mode 100644 index 0000000..22ef2ed --- /dev/null +++ b/src/gifdec.h @@ -0,0 +1,60 @@ +#ifndef GIFDEC_H +#define GIFDEC_H + +#include +#include + +#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 { + int 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); +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 */