efl/src/modules/evas/loaders/gif/evas_image_load_gif.c

1069 lines
30 KiB
C

#include "evas_common_private.h"
#include "evas_private.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <gif_lib.h>
#ifndef MIN
# define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
typedef struct _Gif_Frame Gif_Frame;
typedef enum _Frame_Load_Type
{
LOAD_FRAME_NONE = 0,
LOAD_FRAME_INFO = 1,
LOAD_FRAME_DATA = 2,
LOAD_FRAME_DATA_INFO = 3
} Frame_Load_Type;
struct _Gif_Frame
{
struct {
/* Image descriptor */
int x;
int y;
int w;
int h;
int interlace;
} image_des;
struct {
/* Graphic Control*/
int disposal;
int transparent;
int delay;
int input;
} frame_info;
int bg_val;
};
typedef struct _Evas_Loader_Internal Evas_Loader_Internal;
struct _Evas_Loader_Internal
{
Eina_File *f;
Evas_Image_Load_Opts *opts;
Evas_Image_Animated *animated;
};
static Eina_Bool evas_image_load_specific_frame(Eina_File *f, const Evas_Image_Load_Opts *opts, Evas_Image_Property *prop, Evas_Image_Animated *animated, int frame_index, int *error);
#define byte2_to_int(a,b) (((b)<<8)|(a))
#define FRAME_MAX 1024
/* find specific frame in image entry */
static Eina_Bool
_find_frame(Evas_Image_Animated *animated, int frame_index, Image_Entry_Frame **frame)
{
Eina_List *l;
Image_Entry_Frame *hit_frame = NULL;
if (!animated->frames) return EINA_FALSE;
EINA_LIST_FOREACH(animated->frames, l, hit_frame)
{
if (hit_frame->index == frame_index)
{
*frame = hit_frame;
return EINA_TRUE;
}
}
return EINA_FALSE;
}
static Eina_Bool
_find_close_frame(Evas_Image_Animated *animated, int frame_index, Image_Entry_Frame **frame)
{
Eina_Bool hit = EINA_FALSE;
int i;
i = frame_index -1;
if (!animated->frames) return EINA_FALSE;
for (; i > 0; i--)
{
hit = _find_frame(animated, i, frame);
if (hit)
return EINA_TRUE;
}
return EINA_FALSE;
}
static Eina_Bool
_evas_image_skip_frame(GifFileType *gif, int frame)
{
int remain_frame = 0;
GifRecordType rec;
if (!gif) return EINA_FALSE;
if (frame == 0) return EINA_TRUE; /* no need to skip */
if (frame < 0 || frame > FRAME_MAX) return EINA_FALSE;
remain_frame = frame;
do
{
if (DGifGetRecordType(gif, &rec) == GIF_ERROR) return EINA_FALSE;
if (rec == EXTENSION_RECORD_TYPE)
{
int ext_code;
GifByteType *ext;
ext = NULL;
DGifGetExtension(gif, &ext_code, &ext);
while (ext)
{ /*skip extention */
ext = NULL;
DGifGetExtensionNext(gif, &ext);
}
}
if (rec == IMAGE_DESC_RECORD_TYPE)
{
int img_code;
GifByteType *img;
if (DGifGetImageDesc(gif) == GIF_ERROR) return EINA_FALSE;
remain_frame --;
/* we have to count frame, so use DGifGetCode and skip decoding */
if (DGifGetCode(gif, &img_code, &img) == GIF_ERROR) return EINA_FALSE;
while (img)
{
img = NULL;
DGifGetCodeNext(gif, &img);
}
if (remain_frame < 1) return EINA_TRUE;
}
if (rec == TERMINATE_RECORD_TYPE) return EINA_FALSE; /* end of file */
} while ((rec != TERMINATE_RECORD_TYPE) && (remain_frame > 0));
return EINA_FALSE;
}
static Eina_Bool
_evas_image_load_frame_graphic_info(Image_Entry_Frame *frame, GifByteType *ext)
{
Gif_Frame *gif_frame = NULL;
if ((!frame) || (!ext)) return EINA_FALSE;
gif_frame = (Gif_Frame *) frame->info;
/* transparent */
if ((ext[1] & 0x1) != 0)
gif_frame->frame_info.transparent = ext[4];
else
gif_frame->frame_info.transparent = -1;
gif_frame->frame_info.input = (ext[1] >>1) & 0x1;
gif_frame->frame_info.disposal = (ext[1] >>2) & 0x7;
gif_frame->frame_info.delay = byte2_to_int(ext[2], ext[3]);
return EINA_TRUE;
}
static Eina_Bool
_evas_image_load_frame_image_des_info(GifFileType *gif, Image_Entry_Frame *frame)
{
Gif_Frame *gif_frame = NULL;
if ((!gif) || (!frame)) return EINA_FALSE;
gif_frame = (Gif_Frame *) frame->info;
gif_frame->image_des.x = gif->Image.Left;
gif_frame->image_des.y = gif->Image.Top;
gif_frame->image_des.w = gif->Image.Width;
gif_frame->image_des.h = gif->Image.Height;
gif_frame->image_des.interlace = gif->Image.Interlace;
return EINA_TRUE;
}
#define PIX(_x, _y) rows[yin + _y][xin + _x]
#define CMAP(_v) cmap->Colors[_v]
static void
_expand_gif_rows(GifRowType *rows, ColorMapObject *cmap, DATA32 *ptr,
int rowpix, int xin, int yin, int w, int h,
int x, int y, int alpha, Eina_Bool overwrite)
{
int xx, yy, pix;
DATA32 *p;
if (alpha >= 0)
{
if (overwrite)
{
for (yy = 0; yy < h; yy++)
{
p = ptr + ((y + yy) * rowpix) + x;
for (xx = 0; xx < w; xx++)
{
pix = PIX(xx, yy);
if (pix != alpha)
*p = ARGB_JOIN(0xff, CMAP(pix).Red,
CMAP(pix).Green, CMAP(pix).Blue);
else *p = 0;
p++;
}
}
}
else
{
for (yy = 0; yy < h; yy++)
{
p = ptr + ((y + yy) * rowpix) + x;
for (xx = 0; xx < w; xx++)
{
pix = PIX(xx, yy);
if (pix != alpha)
*p = ARGB_JOIN(0xff, CMAP(pix).Red,
CMAP(pix).Green, CMAP(pix).Blue);
p++;
}
}
}
}
else
{
for (yy = 0; yy < h; yy++)
{
p = ptr + ((y + yy) * rowpix) + x;
for (xx = 0; xx < w; xx++)
{
pix = PIX(xx, yy);
*p = ARGB_JOIN(0xff, CMAP(pix).Red,
CMAP(pix).Green, CMAP(pix).Blue);
p++;
}
}
}
}
static void
_copy_pixels(DATA32 *ptr_src, DATA32 *ptr, int rowpix,
int x, int y, int w, int h)
{
DATA32 *p1, *p2, *pe;
int yy;
if ((w <= 0) || (h <= 0)) return;
for (yy = 0; yy < h; yy++)
{
p1 = ptr_src + ((y + yy) * rowpix) + x;
p2 = ptr + ((y + yy) * rowpix) + x;
for (pe = p2 + w; p2 < pe;) *p2++ = *p1++;
}
}
static void
_fill_pixels(DATA32 val, DATA32 *ptr, int rowpix,
int x, int y, int w, int h)
{
DATA32 *p2, *pe;
int yy;
if ((w <= 0) || (h <= 0)) return;
for (yy = 0; yy < h; yy++)
{
p2 = ptr + ((y + yy) * rowpix) + x;
for (pe = p2 + w; p2 < pe;) *p2++ = val;
}
}
static Eina_Bool
_evas_image_load_frame_image_data(Eina_File *f,
const Evas_Image_Load_Opts *opts EINA_UNUSED,
Evas_Image_Property *prop,
Evas_Image_Animated *animated,
GifFileType *gif, Image_Entry_Frame *frame, int *error)
{
ColorMapObject *cmap;
GifRowType *rows;
DATA32 *ptr, pad = 0xff000000;
Gif_Frame *gif_frame = NULL;
int intoffset[] = { 0, 4, 2, 1 };
int intjump[] = { 8, 8, 4, 2 };
int x, y, w, h, i, bg, alpha, cache_w, cache_h, cur_h, cur_w, yy;
int disposal = 0, xin = 0, yin = 0;
if ((!gif) || (!frame)) return EINA_FALSE;
gif_frame = (Gif_Frame *) frame->info;
w = gif->Image.Width;
h = gif->Image.Height;
x = gif->Image.Left;
y = gif->Image.Top;
cur_h = h;
cur_w = w;
cache_w = prop->w;
cache_h = prop->h;
rows = malloc((h * sizeof(GifRowType *)) + (w * h * sizeof(GifPixelType)));
if (!rows)
{
*error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
return EINA_FALSE;
}
for (yy = 0; yy < h; yy++)
{
rows[yy] = ((unsigned char *)rows) + (h * sizeof(GifRowType *)) +
(yy * w * sizeof(GifPixelType));
}
if (gif->Image.Interlace)
{
for (i = 0; i < 4; i++)
{
for (yy = intoffset[i]; yy < h; yy += intjump[i])
{
if (DGifGetLine(gif, rows[yy], w) != GIF_OK)
{
*error = EVAS_LOAD_ERROR_CORRUPT_FILE;
goto error;
}
}
}
}
else
{
for (yy = 0; yy < h; yy++)
{
if (DGifGetLine(gif, rows[yy], w) != GIF_OK)
{
*error = EVAS_LOAD_ERROR_CORRUPT_FILE;
goto error;
}
}
}
alpha = gif_frame->frame_info.transparent;
if ((prop->alpha) || (alpha >= 0)) pad = 0x00000000;
frame->data = malloc(cache_w * cache_h * sizeof(DATA32));
if (!frame->data)
{
*error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
goto error;
}
bg = gif->SBackGroundColor;
cmap = (gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap);
if (!cmap)
{
DGifCloseFile(gif);
if (frame->data) free(frame->data);
*error = EVAS_LOAD_ERROR_CORRUPT_FILE;
goto error;
}
if (cur_h > cache_h) cur_h = cache_h;
if (cur_w > cache_w) cur_w = cache_w;
// clip the image region to be within current image size and note the inset
if (x < 0)
{
w += x; xin = -x; x = 0;
}
if ((x + w) > cache_w) w = cache_w - x;
if (y < 0)
{
h += y; yin = -y; y = 0;
}
if ((y + h) > cache_h) h = cache_h - y;
// data ptr to write to
ptr = frame->data;
if (frame->index > 1)
{
/* get previous frame only frame index is bigger than 1 */
DATA32 *ptr_src;
Image_Entry_Frame *new_frame = NULL;
int cur_frame = frame->index;
int start_frame = 1;
if (_find_close_frame(animated, cur_frame, &new_frame))
start_frame = new_frame->index + 1;
if ((start_frame < 1) || (start_frame > cur_frame))
{
*error = EVAS_LOAD_ERROR_CORRUPT_FILE;
goto error;
}
/* load previous frame of cur_frame */
for (i = start_frame; i < cur_frame; i++)
{
// FIXME : that one -v
if (!evas_image_load_specific_frame(f, opts, prop, animated, i, error))
{
*error = EVAS_LOAD_ERROR_CORRUPT_FILE;
goto error;
}
}
if (!_find_frame(animated, cur_frame - 1, &new_frame))
{
*error = EVAS_LOAD_ERROR_CORRUPT_FILE;
goto error;
}
else
{
Gif_Frame *gif_frame2 = NULL;
ptr_src = new_frame->data;
if (new_frame->info)
{
gif_frame2 = (Gif_Frame *)(new_frame->info);
disposal = gif_frame2->frame_info.disposal;
gif_frame->bg_val = gif_frame2->bg_val;
switch (disposal)
{
case 0: // no nothing
case 2: // restore bg ... browsers don't respect bg, so neither shall we.
// fill in the area OUTSIDE the image with 0/black
_fill_pixels(pad, ptr, cache_w, 0, 0, cache_w, y);
_fill_pixels(pad, ptr, cache_w, 0, y + h, cache_w, cache_h - (y + h));
_fill_pixels(pad, ptr, cache_w, 0, y, x, h);
_fill_pixels(pad, ptr, cache_w, x + w, y, cache_w - (x + w), h);
_expand_gif_rows(rows, cmap, ptr, cache_w, xin, yin,
w, h, x, y, alpha, EINA_TRUE);
break;
case 1: // leave as-is
case 3: // previous image
_copy_pixels(ptr_src, ptr, cache_w, 0, 0,
cache_w, cache_h);
_expand_gif_rows(rows, cmap, ptr, cache_w, xin, yin,
w, h, x, y, alpha, EINA_FALSE);
break;
default:
break;
}
}
}
}
else /* first frame decoding */
{
/* get the background value */
gif_frame->bg_val = RGB_JOIN(cmap->Colors[bg].Red,
cmap->Colors[bg].Green,
cmap->Colors[bg].Blue);
// fill in the area OUTSIDE the image with 0/black
_fill_pixels(pad, ptr, cache_w, 0, 0, cache_w, y);
_fill_pixels(pad, ptr, cache_w, 0, y + h, cache_w, cache_h - (y + h));
_fill_pixels(pad, ptr, cache_w, 0, y, x, h);
_fill_pixels(pad, ptr, cache_w, x + w, y, cache_w - (x + w), h);
_expand_gif_rows(rows, cmap, ptr, cache_w, xin, yin,
w, h, x, y, alpha, EINA_TRUE);
}
if (rows) free(rows);
frame->loaded = EINA_TRUE;
return EINA_TRUE;
error:
if (rows) free(rows);
return EINA_FALSE;
}
static Eina_Bool
_evas_image_load_frame(Eina_File *f, const Evas_Image_Load_Opts *opts,
Evas_Image_Property *prop, Evas_Image_Animated *animated,
GifFileType *gif, Image_Entry_Frame *frame, Frame_Load_Type type, int *error)
{
GifRecordType rec;
int gra_res = 0, img_res = 0;
Eina_Bool res = EINA_FALSE;
if ((!gif) || (!frame)) return EINA_FALSE;
if (type > LOAD_FRAME_DATA_INFO) return EINA_FALSE;
do
{
if (DGifGetRecordType(gif, &rec) == GIF_ERROR) return EINA_FALSE;
if (rec == IMAGE_DESC_RECORD_TYPE)
{
img_res++;
break;
}
else if (rec == EXTENSION_RECORD_TYPE)
{
int ext_code;
GifByteType *ext;
ext = NULL;
DGifGetExtension(gif, &ext_code, &ext);
while (ext)
{
if (ext_code == 0xf9) /* Graphic Control Extension */
{
gra_res++;
/* fill frame info */
if ((type == LOAD_FRAME_INFO) || (type == LOAD_FRAME_DATA_INFO))
_evas_image_load_frame_graphic_info(frame,ext);
}
ext = NULL;
DGifGetExtensionNext(gif, &ext);
}
}
} while ((rec != TERMINATE_RECORD_TYPE) && (img_res == 0));
if (img_res != 1) return EINA_FALSE;
if (DGifGetImageDesc(gif) == GIF_ERROR) return EINA_FALSE;
if ((type == LOAD_FRAME_INFO) || (type == LOAD_FRAME_DATA_INFO))
_evas_image_load_frame_image_des_info(gif, frame);
if ((type == LOAD_FRAME_DATA) || (type == LOAD_FRAME_DATA_INFO))
{
res = _evas_image_load_frame_image_data(f, opts, prop, animated,
gif, frame, error);
if (!res) return EINA_FALSE;
}
return EINA_TRUE;
}
/* set frame data to cache entry's data */
static Eina_Bool
evas_image_load_file_data_gif_internal(Evas_Image_Property *prop,
Image_Entry_Frame *frame,
void *pixels,
int *error)
{
/* only copy real frame part */
memcpy(pixels, frame->data, prop->w * prop->h * sizeof (DATA32));
prop->premul = EINA_TRUE;
*error = EVAS_LOAD_ERROR_NONE;
return EINA_TRUE;
}
typedef struct _Evas_GIF_Info Evas_GIF_Info;
struct _Evas_GIF_Info
{
unsigned char *map;
int length;
int position;
};
static int
_evas_image_load_file_read(GifFileType* gft, GifByteType *buf,int length)
{
Evas_GIF_Info *egi = gft->UserData;
if (egi->position == egi->length) return 0;
if (egi->position + length == egi->length) length = egi->length - egi->position;
memcpy(buf, egi->map + egi->position, length);
egi->position += length;
return length;
}
static void *
evas_image_load_file_open_gif(Eina_File *f, Eina_Stringshare *key EINA_UNUSED,
Evas_Image_Load_Opts *opts,
Evas_Image_Animated *animated,
int *error)
{
Evas_Loader_Internal *loader;
loader = calloc(1, sizeof (Evas_Loader_Internal));
if (!loader)
{
*error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
return NULL;
}
loader->f = f;
loader->opts = opts;
loader->animated = animated;
return loader;
}
static void
evas_image_load_file_close_gif(void *loader_data)
{
free(loader_data);
}
static Eina_Bool
evas_image_load_file_head_gif(void *loader_data,
Evas_Image_Property *prop,
int *error)
{
Evas_Loader_Internal *loader = loader_data;
// Evas_Image_Load_Opts *load_opts;
Evas_Image_Animated *animated;
Eina_File *f;
Evas_GIF_Info egi;
GifRecordType rec;
GifFileType *gif = NULL;
int loop_count = -1;
int a;
Eina_Bool r = EINA_FALSE;
//it is possible which gif file have error midle of frames,
//in that case we should play gif file until meet error frame.
int image_count = 0;
f = loader->f;
// load_opts = loader->opts;
animated = loader->animated;
prop->w = 0;
prop->h = 0;
a = 0;
egi.map = eina_file_map_all(f, EINA_FILE_RANDOM);
if (!egi.map)
{
*error = EVAS_LOAD_ERROR_CORRUPT_FILE;
goto on_error;
}
egi.length = eina_file_size_get(f);
egi.position = 0;
#if GIFLIB_MAJOR >= 5
gif = DGifOpen(&egi, _evas_image_load_file_read, NULL);
#else
gif = DGifOpen(&egi, _evas_image_load_file_read);
#endif
if (!gif)
{
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
goto on_error;
}
/* check logical screen size */
prop->w = gif->SWidth;
prop->h = gif->SHeight;
/* support scale down feture in gif*/
// disable scale down for gif until gif is handled right
// if (load_opts->scale_down_by > 1)
// {
// prop->w /= load_opts->scale_down_by;
// prop->h /= load_opts->scale_down_by;
// }
if ((prop->w < 1) || (prop->h < 1) ||
(prop->w > IMG_MAX_SIZE) || (prop->h > IMG_MAX_SIZE) ||
IMG_TOO_BIG(prop->w, prop->h))
{
if (IMG_TOO_BIG(prop->w, prop->h))
*error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
else
*error = EVAS_LOAD_ERROR_GENERIC;
goto on_error;
}
do
{
if (DGifGetRecordType(gif, &rec) == GIF_ERROR)
{
if (image_count > 1) break; //we should show normal frames.
/* PrintGifError(); */
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
goto on_error;
}
/* image descript info */
if (rec == IMAGE_DESC_RECORD_TYPE)
{
int img_code;
GifByteType *img;
if (DGifGetImageDesc(gif) == GIF_ERROR)
{
/* PrintGifError(); */
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
goto on_error;
}
/* we have to count frame, so use DGifGetCode and skip decoding */
if (DGifGetCode(gif, &img_code, &img) == GIF_ERROR)
{
/* PrintGifError(); */
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
goto on_error;
}
while (img)
{
img = NULL;
DGifGetCodeNext(gif, &img);
}
image_count++;
}
else if (rec == EXTENSION_RECORD_TYPE)
{
int ext_code;
GifByteType *ext;
ext = NULL;
DGifGetExtension(gif, &ext_code, &ext);
while (ext)
{
if (ext_code == 0xf9) /* Graphic Control Extension */
{
if ((ext[1] & 1) && (prop->alpha == 0))
prop->alpha = (int)ext[4];
}
else if (ext_code == 0xff) /* application extension */
{
if (!strncmp ((char*)(&ext[1]), "NETSCAPE2.0", 11) ||
!strncmp ((char*)(&ext[1]), "ANIMEXTS1.0", 11))
{
ext=NULL;
DGifGetExtensionNext(gif, &ext);
if (ext[1] == 0x01)
{
loop_count = ext[2] + (ext[3] << 8);
if (loop_count > 0) loop_count++;
}
}
}
ext = NULL;
DGifGetExtensionNext(gif, &ext);
}
}
} while (rec != TERMINATE_RECORD_TYPE);
if (a >= 0) prop->alpha = 1;
if ((gif->ImageCount > 1) || (image_count > 1))
{
animated->animated = 1;
animated->loop_count = loop_count;
animated->loop_hint = EVAS_IMAGE_ANIMATED_HINT_LOOP;
animated->frame_count = MIN(gif->ImageCount, image_count);
animated->frames = NULL;
}
*error = EVAS_LOAD_ERROR_NONE;
r = EINA_TRUE;
on_error:
if (gif) DGifCloseFile(gif);
if (egi.map) eina_file_map_free(f, egi.map);
return r;
}
static Eina_Bool
evas_image_load_specific_frame(Eina_File *f,
const Evas_Image_Load_Opts *opts,
Evas_Image_Property *prop,
Evas_Image_Animated *animated, int frame_index,
int *error)
{
GifFileType *gif = NULL;
Image_Entry_Frame *frame = NULL;
Gif_Frame *gif_frame = NULL;
Evas_GIF_Info egi;
Eina_Bool r = EINA_FALSE;
egi.map = eina_file_map_all(f, EINA_FILE_RANDOM);
if (!egi.map)
{
*error = EVAS_LOAD_ERROR_CORRUPT_FILE;
goto on_error;
}
egi.length = eina_file_size_get(f);
egi.position = 0;
#if GIFLIB_MAJOR >= 5
gif = DGifOpen(&egi, _evas_image_load_file_read, NULL);
#else
gif = DGifOpen(&egi, _evas_image_load_file_read);
#endif
if (!gif)
{
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
goto on_error;
}
if (!_evas_image_skip_frame(gif, frame_index-1))
{
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
goto on_error;
}
frame = malloc(sizeof (Image_Entry_Frame));
if (!frame)
{
*error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
goto on_error;
}
gif_frame = calloc(1, sizeof (Gif_Frame));
if (!gif_frame)
{
*error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
free(frame);
goto on_error;
}
frame->info = gif_frame;
frame->index = frame_index;
if (!_evas_image_load_frame(f, opts, prop, animated, gif, frame, LOAD_FRAME_DATA_INFO, error))
{
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
free(gif_frame);
free(frame);
goto on_error;
}
animated->frames = eina_list_append(animated->frames, frame);
r = EINA_TRUE;
on_error:
if (gif) DGifCloseFile(gif);
if (egi.map) eina_file_map_free(f, egi.map);
return r;
}
static Eina_Bool
evas_image_load_file_data_gif(void *loader_data,
Evas_Image_Property *prop,
void *pixels, int *error)
{
Evas_Loader_Internal *loader = loader_data;
Evas_Image_Load_Opts *opts;
Evas_Image_Animated *animated;
Eina_File *f;
Image_Entry_Frame *frame = NULL;
int cur_frame_index;
Eina_Bool hit;
opts = loader->opts;
animated = loader->animated;
f = loader->f;
if(!animated->animated)
cur_frame_index = 1;
else
cur_frame_index = animated->cur_frame;
if ((animated->animated) &&
((cur_frame_index < 0) || (cur_frame_index > FRAME_MAX) || (cur_frame_index > animated->frame_count)))
{
*error = EVAS_LOAD_ERROR_GENERIC;
return EINA_FALSE;
}
/* first time frame is set to be 0. so default is 1 */
if (cur_frame_index == 0) cur_frame_index++;
/* Check current frame exists in hash table */
hit = _find_frame(animated, cur_frame_index, &frame);
/* if current frame exist in has table, check load flag */
if (hit)
{
if (frame->loaded)
{
evas_image_load_file_data_gif_internal(prop, frame, pixels, error);
}
else
{
Evas_GIF_Info egi;
GifFileType *gif = NULL;
Eina_Bool r = EINA_FALSE;
egi.map = eina_file_map_all(f, EINA_FILE_RANDOM);
if (!egi.map)
{
*error = EVAS_LOAD_ERROR_CORRUPT_FILE;
goto on_error;
}
egi.length = eina_file_size_get(f);
egi.position = 0;
#if GIFLIB_MAJOR >= 5
gif = DGifOpen(&egi, _evas_image_load_file_read, NULL);
#else
gif = DGifOpen(&egi, _evas_image_load_file_read);
#endif
if (!gif)
{
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
goto on_error;
}
_evas_image_skip_frame(gif, cur_frame_index - 1);
if (!_evas_image_load_frame(f, opts, prop, animated, gif, frame, LOAD_FRAME_DATA, error))
{
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
goto on_error;
}
if (!evas_image_load_file_data_gif_internal(prop, frame, pixels, error))
{
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
goto on_error;
}
*error = EVAS_LOAD_ERROR_NONE;
r = EINA_TRUE;
on_error:
if (gif) DGifCloseFile(gif);
if (egi.map) eina_file_map_free(f, egi.map);
return r;
}
}
/* current frame does is not exist */
else
{
if (!evas_image_load_specific_frame(f, opts, prop, animated, cur_frame_index, error))
return EINA_FALSE;
hit = EINA_FALSE;
frame = NULL;
hit = _find_frame(animated, cur_frame_index, &frame);
if (!hit) return EINA_FALSE;
if (!evas_image_load_file_data_gif_internal(prop, frame, pixels, error))
{
*error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
return EINA_FALSE;
}
return EINA_TRUE;
}
return EINA_FALSE;
}
static double
evas_image_load_frame_duration_gif(void *loader_data,
int start_frame, int frame_num)
{
Evas_Loader_Internal *loader = loader_data;
Evas_Image_Animated *animated;
Eina_File *f;
Evas_GIF_Info egi;
GifFileType *gif = NULL;
GifRecordType rec;
int current_frame = 1;
int remain_frames = frame_num;
double duration = -1;
int frame_count = 0;
animated = loader->animated;
f = loader->f;
frame_count = animated->frame_count;
if (!animated->animated) return -1;
if ((start_frame + frame_num) > frame_count) return -1;
if (frame_num < 0) return -1;
egi.map = eina_file_map_all(f, EINA_FILE_RANDOM);
if (!egi.map) goto on_error;
egi.length = eina_file_size_get(f);
egi.position = 0;
#if GIFLIB_MAJOR >= 5
gif = DGifOpen(&egi, _evas_image_load_file_read, NULL);
#else
gif = DGifOpen(&egi, _evas_image_load_file_read);
#endif
if (!gif) goto on_error;
duration = 0;
do
{
if (DGifGetRecordType(gif, &rec) == GIF_ERROR)
{
rec = TERMINATE_RECORD_TYPE;
}
if (rec == IMAGE_DESC_RECORD_TYPE)
{
int img_code;
GifByteType *img;
if (DGifGetImageDesc(gif) == GIF_ERROR)
{
/* PrintGifError(); */
rec = TERMINATE_RECORD_TYPE;
}
current_frame++;
/* we have to count frame, so use DGifGetCode and skip decoding */
if (DGifGetCode(gif, &img_code, &img) == GIF_ERROR)
{
rec = TERMINATE_RECORD_TYPE;
}
while (img)
{
img = NULL;
DGifGetExtensionNext(gif, &img);
}
}
else if (rec == EXTENSION_RECORD_TYPE)
{
int ext_code;
GifByteType *ext;
ext = NULL;
DGifGetExtension(gif, &ext_code, &ext);
while (ext)
{
if (ext_code == 0xf9) /* Graphic Control Extension */
{
if ((current_frame >= start_frame) && (current_frame <= frame_count))
{
int frame_duration = 0;
if (remain_frames < 0) break;
frame_duration = byte2_to_int (ext[2], ext[3]);
if (frame_duration == 0)
duration += 0.1;
else
duration += (double)frame_duration/100;
remain_frames --;
}
}
ext = NULL;
DGifGetExtensionNext(gif, &ext);
}
}
} while (rec != TERMINATE_RECORD_TYPE);
on_error:
if (gif) DGifCloseFile(gif);
if (egi.map) eina_file_map_free(f, egi.map);
return duration;
}
static Evas_Image_Load_Func evas_image_load_gif_func =
{
evas_image_load_file_open_gif,
evas_image_load_file_close_gif,
evas_image_load_file_head_gif,
evas_image_load_file_data_gif,
evas_image_load_frame_duration_gif,
EINA_TRUE,
EINA_FALSE
};
static int
module_open(Evas_Module *em)
{
if (!em) return 0;
em->functions = (void *)(&evas_image_load_gif_func);
return 1;
}
static void
module_close(Evas_Module *em EINA_UNUSED)
{
}
static Evas_Module_Api evas_modapi =
{
EVAS_MODULE_API_VERSION,
"gif",
"none",
{
module_open,
module_close
}
};
EVAS_MODULE_DEFINE(EVAS_MODULE_TYPE_IMAGE_LOADER, image_loader, gif);
#ifndef EVAS_STATIC_BUILD_GIF
EVAS_EINA_MODULE_DEFINE(image_loader, gif);
#endif