#include "gifdec.h"
#include "../../misc/lv_log.h"
#include "../../stdlib/lv_mem.h"
#include "../../misc/lv_color.h"
#if LV_USE_GIF

#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#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;

#if LV_GIF_CACHE_DECODE_DATA
#define LZW_MAXBITS                 12
#define LZW_TABLE_SIZE              (1 << LZW_MAXBITS)
#define LZW_CACHE_SIZE              (LZW_TABLE_SIZE * 4)
#endif

static gd_GIF  * gif_open(gd_GIF * gif);
static bool f_gif_open(gd_GIF * gif, const void * path, bool is_file);
static void f_gif_read(gd_GIF * gif, void * buf, size_t len);
static int f_gif_seek(gd_GIF * gif, size_t pos, int k);
static void f_gif_close(gd_GIF * gif);

#if LV_USE_DRAW_SW_ASM == LV_DRAW_SW_ASM_HELIUM
    #include "gifdec_mve.h"
#endif

static uint16_t
read_num(gd_GIF * gif)
{
    uint8_t bytes[2];

    f_gif_read(gif, bytes, 2);
    return bytes[0] + (((uint16_t) bytes[1]) << 8);
}

gd_GIF *
gd_open_gif_file(const char * fname)
{
    gd_GIF gif_base;
    memset(&gif_base, 0, sizeof(gif_base));

    bool res = f_gif_open(&gif_base, fname, true);
    if(!res) return NULL;

    return gif_open(&gif_base);
}

gd_GIF *
gd_open_gif_data(const void * data)
{
    gd_GIF gif_base;
    memset(&gif_base, 0, sizeof(gif_base));

    bool res = f_gif_open(&gif_base, data, false);
    if(!res) return NULL;

    return gif_open(&gif_base);
}

static gd_GIF * gif_open(gd_GIF * gif_base)
{
    uint8_t sigver[3];
    uint16_t width, height, depth;
    uint8_t fdsz, bgidx, aspect;
    uint8_t * bgcolor;
    int gct_sz;
    gd_GIF * gif = NULL;

    /* Header */
    f_gif_read(gif_base, sigver, 3);
    if(memcmp(sigver, "GIF", 3) != 0) {
        LV_LOG_WARN("invalid signature\n");
        goto fail;
    }
    /* Version */
    f_gif_read(gif_base, sigver, 3);
    if(memcmp(sigver, "89a", 3) != 0) {
        LV_LOG_WARN("invalid version\n");
        goto fail;
    }
    /* Width x Height */
    width  = read_num(gif_base);
    height = read_num(gif_base);
    /* FDSZ */
    f_gif_read(gif_base, &fdsz, 1);
    /* Presence of GCT */
    if(!(fdsz & 0x80)) {
        LV_LOG_WARN("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 */
    f_gif_read(gif_base, &bgidx, 1);
    /* Aspect Ratio */
    f_gif_read(gif_base, &aspect, 1);
    /* Create gd_GIF Structure. */
#if LV_GIF_CACHE_DECODE_DATA
    gif = lv_malloc(sizeof(gd_GIF) + 5 * width * height + LZW_CACHE_SIZE);
    #else
    gif = lv_malloc(sizeof(gd_GIF) + 5 * width * height);
    #endif
    if(!gif) goto fail;
    memcpy(gif, gif_base, sizeof(gd_GIF));
    gif->width  = width;
    gif->height = height;
    gif->depth  = depth;
    /* Read GCT */
    gif->gct.size = gct_sz;
    f_gif_read(gif, gif->gct.colors, 3 * gif->gct.size);
    gif->palette = &gif->gct;
    gif->bgindex = bgidx;
    gif->canvas = (uint8_t *) &gif[1];
    gif->frame = &gif->canvas[4 * width * height];
    if(gif->bgindex) {
        memset(gif->frame, gif->bgindex, gif->width * gif->height);
    }
    bgcolor = &gif->palette->colors[gif->bgindex * 3];
    #if LV_GIF_CACHE_DECODE_DATA
    gif->lzw_cache = gif->frame + width * height;
    #endif

#ifdef GIFDEC_FILL_BG
    GIFDEC_FILL_BG(gif->canvas, gif->width * gif->height, 1, gif->width * gif->height, bgcolor, 0xff);
#else
    for(int i = 0; i < gif->width * gif->height; i++) {
        gif->canvas[i * 4 + 0] = *(bgcolor + 2);
        gif->canvas[i * 4 + 1] = *(bgcolor + 1);
        gif->canvas[i * 4 + 2] = *(bgcolor + 0);
        gif->canvas[i * 4 + 3] = 0xff;
    }
#endif
    gif->anim_start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
    gif->loop_count = -1;
    goto ok;
fail:
    f_gif_close(gif_base);
ok:
    return gif;
}

static void
discard_sub_blocks(gd_GIF * gif)
{
    uint8_t size;

    do {
        f_gif_read(gif, &size, 1);
        f_gif_seek(gif, size, LV_FS_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;
        size_t sub_block;
        f_gif_seek(gif, 1, LV_FS_SEEK_CUR); /* block size = 12 */
        tx = read_num(gif);
        ty = read_num(gif);
        tw = read_num(gif);
        th = read_num(gif);
        f_gif_read(gif, &cw, 1);
        f_gif_read(gif, &ch, 1);
        f_gif_read(gif, &fg, 1);
        f_gif_read(gif, &bg, 1);
        sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
        gif->plain_text(gif, tx, ty, tw, th, cw, ch, fg, bg);
        f_gif_seek(gif, sub_block, LV_FS_SEEK_SET);
    }
    else {
        /* Discard plain text metadata. */
        f_gif_seek(gif, 13, LV_FS_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). */
    f_gif_seek(gif, 1, LV_FS_SEEK_CUR);
    f_gif_read(gif, &rdit, 1);
    gif->gce.disposal = (rdit >> 2) & 3;
    gif->gce.input = rdit & 2;
    gif->gce.transparency = rdit & 1;
    gif->gce.delay = read_num(gif);
    f_gif_read(gif, &gif->gce.tindex, 1);
    /* Skip block terminator. */
    f_gif_seek(gif, 1, LV_FS_SEEK_CUR);
}

static void
read_comment_ext(gd_GIF * gif)
{
    if(gif->comment) {
        size_t sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
        gif->comment(gif);
        f_gif_seek(gif, sub_block, LV_FS_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];
    uint16_t loop_count;

    /* Discard block size (always 0x0B). */
    f_gif_seek(gif, 1, LV_FS_SEEK_CUR);
    /* Application Identifier. */
    f_gif_read(gif, app_id, 8);
    /* Application Authentication Code. */
    f_gif_read(gif, app_auth_code, 3);
    if(!strncmp(app_id, "NETSCAPE", sizeof(app_id))) {
        /* Discard block size (0x03) and constant byte (0x01). */
        f_gif_seek(gif, 2, LV_FS_SEEK_CUR);
        loop_count = read_num(gif);
        if(gif->loop_count < 0) {
            if(loop_count == 0) {
                gif->loop_count = 0;
            }
            else {
                gif->loop_count = loop_count + 1;
            }
        }
        /* Skip block terminator. */
        f_gif_seek(gif, 1, LV_FS_SEEK_CUR);
    }
    else if(gif->application) {
        size_t sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
        gif->application(gif, app_id, app_auth_code);
        f_gif_seek(gif, sub_block, LV_FS_SEEK_SET);
        discard_sub_blocks(gif);
    }
    else {
        discard_sub_blocks(gif);
    }
}

static void
read_ext(gd_GIF * gif)
{
    uint8_t label;

    f_gif_read(gif, &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:
            LV_LOG_WARN("unknown extension: %02X\n", label);
    }
}

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) {
                f_gif_read(gif, sub_len, 1); /* Must be nonzero! */
                if (*sub_len == 0) return 0x1000;
            }
            f_gif_read(gif, 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;
}

#if LV_GIF_CACHE_DECODE_DATA
static int
read_image_data(gd_GIF *gif, int interlace)
{
    uint8_t sub_len, shift, byte;
    int ret = 0;
    int key_size;
    int y, pass, linesize;
    uint8_t *ptr = NULL;
    uint8_t *ptr_row_start = NULL;
    uint8_t *ptr_base = NULL;
    size_t start, end;
    uint16_t key, clear_code, stop_code, curr_code;
    int frm_off, frm_size,curr_size,top_slot,new_codes,slot;
    /* The first value of the value sequence corresponding to key */
    int first_value;
    int last_key;
    uint8_t *sp = NULL;
    uint8_t *p_stack = NULL;
    uint8_t *p_suffix = NULL;
    uint16_t *p_prefix = NULL;

    /* get initial key size and clear code, stop code */
    f_gif_read(gif, &byte, 1);
    key_size = (int) byte;
    clear_code = 1 << key_size;
    stop_code = clear_code + 1;
    key = 0;

    start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
    discard_sub_blocks(gif);
    end = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
    f_gif_seek(gif, start, LV_FS_SEEK_SET);

    linesize = gif->width;
    ptr_base = &gif->frame[gif->fy * linesize + gif->fx];
    ptr_row_start = ptr_base;
    ptr = ptr_row_start;
    sub_len = shift = 0;
    /* decoder */
    pass = 0;
    y = 0;
    p_stack = gif->lzw_cache;
    p_suffix = gif->lzw_cache + LZW_TABLE_SIZE;
    p_prefix = (uint16_t*)(gif->lzw_cache + LZW_TABLE_SIZE * 2);
    frm_off = 0;
    frm_size = gif->fw * gif->fh;
    curr_size = key_size + 1;
    top_slot = 1 << curr_size;
    new_codes = clear_code + 2;
    slot = new_codes;
    first_value = -1;
    last_key = -1;
    sp = p_stack;

    while (frm_off < frm_size) {
        /* copy data to frame buffer */
        while (sp > p_stack) {
            *ptr++ = *(--sp);
            frm_off += 1;
            /* read one line */
            if ((ptr - ptr_row_start) == gif->fw) {
                if (interlace) {
                    switch(pass) {
                    case 0:
                    case 1:
                        y += 8;
                        ptr_row_start += linesize * 8;
                        break;
                    case 2:
                        y += 4;
                        ptr_row_start += linesize * 4;
                        break;
                    case 3:
                        y += 2;
                        ptr_row_start += linesize * 2;
                        break;
                    default:
                        break;
                    }
                    while (y >= gif->fh) {
                        y  = 4 >> pass;
                        ptr_row_start = ptr_base + linesize * y;
                        pass++;
                    }
                } else {
                    ptr_row_start += linesize;
                }
                ptr = ptr_row_start;
            }
        }

        key = get_key(gif, curr_size, &sub_len, &shift, &byte);

        if (key == stop_code || key >= LZW_TABLE_SIZE)
            break;

        if (key == clear_code) {
            curr_size = key_size + 1;
            slot = new_codes;
            top_slot = 1 << curr_size;
            first_value = last_key = -1;
            sp = p_stack;
            continue;
        }

        curr_code = key;
        /*
         * If the current code is a code that will be added to the decoding
         * dictionary, it is composed of the data list corresponding to the
         * previous key and its first data.
         * */
        if (curr_code == slot && first_value >= 0) {
            *sp++ = first_value;
            curr_code = last_key;
        }else if(curr_code >= slot)
            break;

        while (curr_code >= new_codes) {
            *sp++ = p_suffix[curr_code];
            curr_code = p_prefix[curr_code];
        }
        *sp++ = curr_code;

        /* Add code to decoding dictionary */
        if (slot < top_slot && last_key >= 0) {
            p_suffix[slot] = curr_code;
            p_prefix[slot++] = last_key;
        }
        first_value = curr_code;
        last_key = key;
        if (slot >= top_slot) {
            if (curr_size < LZW_MAXBITS) {
                top_slot <<= 1;
                curr_size += 1;
            }
        }
    }

    if (key == stop_code) f_gif_read(gif, &sub_len, 1); /* Must be zero! */
    f_gif_seek(gif, end, LV_FS_SEEK_SET);
    return ret;
}
#else
static Table *
new_table(int key_size)
{
    int key;
    int init_bulk = MAX(1 << (key_size + 1), 0x100);
    Table * table = lv_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 = lv_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;
}

/* 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 = 0;
    int frm_off, frm_size, str_len = 0, i, p, x, y;
    uint16_t key, clear, stop;
    int ret;
    Table * table;
    Entry entry = {0};
    size_t start, end;

    f_gif_read(gif, &byte, 1);
    key_size = (int) byte;
    start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
    discard_sub_blocks(gif);
    end = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
    f_gif_seek(gif, start, LV_FS_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) {
                lv_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;
    }
    lv_free(table);
    if(key == stop) f_gif_read(gif, &sub_len, 1);  /* Must be zero! */
    f_gif_seek(gif, end, LV_FS_SEEK_SET);
    return 0;
}

#endif

/* 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);
    gif->fy = read_num(gif);
    gif->fw = read_num(gif);
    gif->fh = read_num(gif);
    f_gif_read(gif, &fisrz, 1);
    interlace = fisrz & 0x40;
    /* Ignore Sort Flag. */
    /* Local Color Table? */
    if(fisrz & 0x80) {
        /* Read LCT */
        gif->lct.size = 1 << ((fisrz & 0x07) + 1);
        f_gif_read(gif, 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 = gif->fy * gif->width + gif->fx;
#ifdef GIFDEC_RENDER_FRAME
    GIFDEC_RENDER_FRAME(&buffer[i * 4], gif->fw, gif->fh, gif->width,
                        &gif->frame[i], gif->palette->colors,
                        gif->gce.transparency ? gif->gce.tindex : 0x100);
#else
    int j, k;
    uint8_t index, * color;

    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) {
                buffer[(i + k) * 4 + 0] = *(color + 2);
                buffer[(i + k) * 4 + 1] = *(color + 1);
                buffer[(i + k) * 4 + 2] = *(color + 0);
                buffer[(i + k) * 4 + 3] = 0xFF;
            }
        }
        i += gif->width;
    }
#endif
}

static void
dispose(gd_GIF * gif)
{
    int i;
    uint8_t * bgcolor;
    switch(gif->gce.disposal) {
        case 2: /* Restore to background color. */
            bgcolor = &gif->palette->colors[gif->bgindex * 3];

            uint8_t opa = 0xff;
            if(gif->gce.transparency) opa = 0x00;

            i = gif->fy * gif->width + gif->fx;
#ifdef GIFDEC_FILL_BG
            GIFDEC_FILL_BG(&(gif->canvas[i * 4]), gif->fw, gif->fh, gif->width, bgcolor, opa);
#else
            int j, k;
            for(j = 0; j < gif->fh; j++) {
                for(k = 0; k < gif->fw; k++) {
                    gif->canvas[(i + k) * 4 + 0] = *(bgcolor + 2);
                    gif->canvas[(i + k) * 4 + 1] = *(bgcolor + 1);
                    gif->canvas[(i + k) * 4 + 2] = *(bgcolor + 0);
                    gif->canvas[(i + k) * 4 + 3] = opa;
                }
                i += gif->width;
            }
#endif
            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);
    f_gif_read(gif, &sep, 1);
    while(sep != ',') {
        if(sep == ';') {
            f_gif_seek(gif, gif->anim_start, LV_FS_SEEK_SET);
            if(gif->loop_count == 1 || gif->loop_count < 0) {
                return 0;
            }
            else if(gif->loop_count > 1) {
                gif->loop_count--;
            }
        }
        else if(sep == '!')
            read_ext(gif);
        else return -1;
        f_gif_read(gif, &sep, 1);
    }
    if(read_image(gif) == -1)
        return -1;
    return 1;
}

void
gd_render_frame(gd_GIF * gif, uint8_t * buffer)
{
    render_frame_rect(gif, buffer);
}

void
gd_rewind(gd_GIF * gif)
{
    gif->loop_count = -1;
    f_gif_seek(gif, gif->anim_start, LV_FS_SEEK_SET);
}

void
gd_close_gif(gd_GIF * gif)
{
    f_gif_close(gif);
    lv_free(gif);
}

static bool f_gif_open(gd_GIF * gif, const void * path, bool is_file)
{
    gif->f_rw_p = 0;
    gif->data = NULL;
    gif->is_file = is_file;

    if(is_file) {
        lv_fs_res_t res = lv_fs_open(&gif->fd, path, LV_FS_MODE_RD);
        if(res != LV_FS_RES_OK) return false;
        else return true;
    }
    else {
        gif->data = path;
        return true;
    }
}

static void f_gif_read(gd_GIF * gif, void * buf, size_t len)
{
    if(gif->is_file) {
        lv_fs_read(&gif->fd, buf, len, NULL);
    }
    else {
        memcpy(buf, &gif->data[gif->f_rw_p], len);
        gif->f_rw_p += len;
    }
}

static int f_gif_seek(gd_GIF * gif, size_t pos, int k)
{
    if(gif->is_file) {
        lv_fs_seek(&gif->fd, pos, k);
        uint32_t x;
        lv_fs_tell(&gif->fd, &x);
        return x;
    }
    else {
        if(k == LV_FS_SEEK_CUR) gif->f_rw_p += pos;
        else if(k == LV_FS_SEEK_SET) gif->f_rw_p = pos;
        return gif->f_rw_p;
    }
}

static void f_gif_close(gd_GIF * gif)
{
    if(gif->is_file) {
        lv_fs_close(&gif->fd);
    }
}

#endif /*LV_USE_GIF*/
